ctx.ReadJSON()是读取JSON请求体的正确入口,它自动校验Content-Type、复用缓冲区并处理EOF和编码问题,比手动ReadAll+Unmarshal更安全;ctx.JSON()仅用于响应端序列化输出。

ctx.ReadJSON() 是读取 JSON 请求体的正确入口
很多人看到 ctx.JSON() 就下意识认为它负责“接收 JSON”,其实它只用于响应端序列化输出。真正读取客户端发来的 JSON 数据,必须用 ctx.ReadJSON()——它会自动校验 Content-Type: application/json、复用缓冲区、处理 EOF 和编码问题,比手动 io.ReadAll + json.Unmarshal 更安全可靠。
常见错误现象:invalid character ',本质是前端没发 JSON(比如返回了 HTML 错误页),或漏设请求头;<code>json: cannot unmarshal string into Go struct field X.Y of type int,说明字段类型不匹配,但 ReadJSON 不会跳过,直接报错。
-
ctx.ReadJSON(&v)要求v是结构体指针,且所有需绑定的字段必须首字母大写(导出) - 结构体 tag 必须显式写
json:"field_name",否则默认按字段名匹配(大小写敏感) - 请求中多出的字段会被静默忽略;如需严格校验,得配合
go-playground/validator的DisallowUnknownFields - 不推荐在 handler 里先
ctx.Request().Body手动读取,会破坏流,后续ReadJSON拿不到数据
什么时候该用 ctx.ReadBody() 而不是 ReadJSON()
ctx.ReadBody(&v) 底层调用 io.ReadFull,只负责把原始字节读进 v,不做任何 Content-Type 检查或 JSON 解析。它适合这些场景:
- 前端用
Content-Type: text/plain发送了一段合法 JSON 字符串(比如某些测试工具或遗留系统) - 你需要先检查原始 payload 再决定是否解析(例如做签名验证、日志采样)
- 你打算用
json.RawMessage延迟解析某嵌套字段,避免一次性反序列化开销
注意:ReadBody 不会帮你转成结构体,你仍需自己调 json.Unmarshal;如果目标 v 是字符串或字节切片,它才直接填值。
结构体字段绑定失败的三个高频原因
即使调用了 ReadJSON 且没报错,也可能出现字段为空——不是框架问题,而是 Go 标准库 encoding/json 的行为限制:
- 字段未导出:比如写
name string,首字母小写,json包完全不可见,绑定永远为零值 - tag 名不一致:请求体是
{"user_name":"a"},但结构体写UserName string `json:"username"`,字段就对不上 - 嵌套结构体或切片类型错误:比如子字段定义为
Children []TreeNode(值类型),会导致无限递归编译失败;必须用[]*TreeNode或*[]*TreeNode
调试建议:先打印原始 body 字节(用 ReadBody 读到 []byte),确认内容和格式无误,再排查结构体定义。
POST/PUT 接口收不到 JSON 的真实原因
400 或静默失败,往往不是代码写错,而是环境链路断在前面:
- 前端 fetch/fetch-like 库没设
headers: {'Content-Type': 'application/json'},Iris 直接拒绝解析 - 反向代理(Nginx/Caddy)或网关重写了
Content-Type,或者截断了 body(比如 Nginx 默认client_max_body_size 1m) - 中间件提前调用了
ctx.StopExecution()或 panic,导致 handler 根本没执行到ReadJSON这行 - 路由注册错误:写了
app.Post却用 PUT 请求调用,或 CORS 预检(OPTIONS)被拦截,导致实际请求发不出去
最容易被忽略的是:Iris 不会自动启用 PUT/DELETE 路由,app.Put("/x", h) 必须显式注册,不能指望它从 POST fallback。调试时先 curl 测试裸请求,绕过前端 JS 环境干扰。


















