接收嵌套JSON数组时struct tag必须用json而非form,因Gin默认用ShouldBindJSON解析且只识别json tag;若误用form或遗漏,嵌套数组字段会静默为空,校验失效。

接收嵌套 JSON 数组时 struct tag 必须用 json 而非 form
Gin 默认用 c.ShouldBindJSON() 解析请求体,它只认 json tag;若误写成 form 或漏写,嵌套数组字段会静默为空(尤其 []struct{} 类型),校验永远通过。常见错误是把表单上传逻辑的 tag 复制过来,结果后端收不到数据。
正确写法示例:
type Request struct {
Users []User `json:"users" binding:"required,min=1,max=10"`
}
type User struct {
Name string `json:"name" binding:"required,min=2"`
Age int `json:"age" binding:"required,gt=0,lt=150"`
}
-
json:"users"是必须的,binding标签才生效 -
min=1,max=10作用于[]User长度,不是单个User字段 - 如果数组字段允许为空,去掉
required,仅保留max=10即可
binding:"min=1" 对空数组不报错?检查是否启用了 ShouldBindJSON 而非 ShouldBind
用 c.ShouldBind() 会触发 multipart/form-data 解析逻辑,对 JSON 请求体解析失败且不报错,导致 min/max 校验被跳过。必须显式用 ShouldBindJSON() 才能保证结构体字段和 binding 规则完整生效。
- 错误调用:
c.ShouldBind(&req)→ 可能返回nil错误但req.Users为空切片 - 正确调用:
if err := c.ShouldBindJSON(&req); err != nil { ... } - 若同时支持 JSON 和表单,需先判断
c.GetHeader("Content-Type")再分路处理
嵌套数组长度校验失效的典型场景:字段类型是 []*User 或含指针字段
当嵌套结构体字段含指针(如 *string、*int)或整个数组是 []*User,Gin 的默认 binding 不会初始化 nil 指针成员,required 校验可能绕过。更严重的是,min/max 仍作用于切片本身,但内部字段校验容易静默失败。
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
- 避免用
[]*User,改用[]User(值类型更可控) - 若必须用指针字段,为每个指针加
binding:"required",例如:Name *string `json:"name" binding:"required"` - 注意:空 JSON 数组
"users": []会被解码为长度 0 的切片,min=1此时必然报错;但"users": null会导致切片为nil,此时min=1不触发(Go 切片 nil 长度为 0,但 binding 包对 nil 切片的 min/max 行为不稳定,建议统一用required拦住)
自定义校验函数处理复杂嵌套数组逻辑(如去重、依赖校验)
内置 min/max 只管长度,无法校验「每个 User.Name 不重复」或「Age 总和不能超 500」这类业务规则。这时得用自定义函数,注册到 Gin 的 validator 实例中。
示例:校验 users 中 name 是否有重复:
import "github.com/go-playground/validator/v10"
func validateUniqueNames(fl validator.FieldLevel) bool {
users, ok := fl.Field().Interface().([]User)
if !ok {
return false
}
seen := make(map[string]bool)
for _, u := range users {
if seen[u.Name] {
return false
}
seen[u.Name] = true
}
return true
}
// 注册
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("unique_names", validateUniqueNames)
}
// 使用
type Request struct {
Users []User `json:"users" binding:"required,min=1,max=10,unique_names"`
}
注意:自定义函数里不要做耗时操作(如 DB 查询),否则阻塞 HTTP 请求;复杂逻辑建议放在 Bind 之后、业务处理之前。
真正容易被忽略的是 nil 切片与空切片的行为差异,以及 ShouldBindJSON 被误替换成 ShouldBind 后的静默失败——这两点几乎占了嵌套数组校验问题的八成。

















