c.shouldbindjson(&users) 可正确绑定 json 数组到结构体切片,需确保:传入切片指针、content-type 为 application/json、结构体含匹配的 json tag;否则报 invalid character '[' 错误。

接收JSON数组时绑定失败,BindJSON 报错 invalid character
Gin 默认的 BindJSON 期望请求体是 JSON 对象(即以 { 开头),如果直接传入 JSON 数组(如 [{"name":"a"},{"name":"b"}]),会触发解析错误:invalid character '[' looking for beginning of value。这不是 Gin 的 bug,而是 Go 标准库 json.Unmarshal 的行为限制——它默认不接受顶层为数组的输入。
- 必须显式声明目标类型为切片,例如
[]map[string]interface{}或自定义结构体切片 - 不能用
BindJSON(&v)直接绑定到未初始化的 nil 切片变量,需提前分配空间或使用指针 - 推荐用
c.ShouldBindJSON(&v)替代c.BindJSON(&v),前者在失败时不自动返回 400,便于统一错误处理
用 ShouldBindJSON 正确解析 JSON 数组到结构体切片
最稳妥的方式是定义结构体,再声明切片变量并传地址给 ShouldBindJSON:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func handler(c *gin.Context) {
var users []User
if err := c.ShouldBindJSON(&users); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// users 现在是正常解析出的切片
}
- 注意
&users是取地址,不是users;Gin 需要可写入的指针 - 如果 JSON 数组为空
[],users会是长度为 0 的切片,不会 panic - 字段标签(如
json:"name")必须与 JSON key 完全匹配,大小写敏感
解析失败常见原因:Content-Type 缺失或错误
Gin 的 ShouldBindJSON 依赖请求头中的 Content-Type: application/json 才会尝试 JSON 解析。如果前端发请求时没设这个 header,Gin 会跳过 JSON 绑定逻辑,导致 users 保持零值且无报错。
- curl 测试时务必加
-H "Content-Type: application/json" - 前端 fetch 要显式设置
headers: { 'Content-Type': 'application/json' } - Postman 中检查 “Body → raw → JSON” 模式是否启用(它会自动加 header)
- 可通过
c.GetHeader("Content-Type")日志确认实际收到的 header
需要动态结构时,用 []map[string]interface{} 或 json.RawMessage
当 JSON 数组结构不固定(比如字段名动态、类型混杂),硬编码结构体会很麻烦。此时有两种轻量方案:
- 用
[]map[string]interface{}:能快速读取任意 key,但所有数值默认是float64,需手动类型断言 - 用
[]json.RawMessage:把每个数组元素当作原始字节缓存,延迟解析,适合做校验或转发
示例:
var raw []json.RawMessage
if err := c.ShouldBindJSON(&raw); err != nil {
// ...
}
for _, item := range raw {
var m map[string]interface{}
if err := json.Unmarshal(item, &m); err != nil {
continue
}
// 处理 m
}
这种写法绕过了 Gin 的结构体映射逻辑,灵活性高,但失去了字段校验和类型安全。真正需要强约束时,还是优先定义结构体。
数组解析本身不难,关键在于理解 Gin 绑定机制依赖 Go 的 json 包规则,以及 Content-Type 和指针传递这两个最容易被忽略的环节。











