
Gin 的 BindJSON 要求传入指向结构体的指针(即 &struct{}),而非指向结构体指针的指针(即 &*struct{});若误传二级指针(如 &json 且 json 本身是 *foos),校验将被跳过,导致 binding:"required" 失效。
gin 的 `bindjson` 要求传入**指向结构体的指针**(即 `&struct{}`),而非**指向结构体指针的指针**(即 `&*struct{}`);若误传二级指针(如 `&json` 且 `json` 本身是 `*foos`),校验将被跳过,导致 `binding:"required"` 失效。
在 Gin 框架中,c.BindJSON(&v) 的底层依赖 StructValidator.ValidateStruct 接口实现字段校验(如 binding:"required")。根据 Gin 源码 binding.go 明确约定:*仅当传入参数是结构体本身或指向结构体的指针(struct{} 或 `struct{})时,校验才会执行;若传入的是指向指针的指针(例如struct{}),则直接跳过校验并返回nil` 错误。
你最初的代码:
func sendFoo(c *gin.Context) {
var json *foos // ← json 是 *foos 类型(一级指针)
if err := c.BindJSON(&json); err != nil { // ← &json 是 **foos(二级指针)!
c.AbortWithStatus(400)
return
}
// ...
}
此时 &json 的类型为 **foos,ValidateStruct 认为这不是合法的结构体或一级指针,因此不执行任何绑定校验——即使 JSON 缺少 foo 字段,也不会触发 required 规则,err 恒为 nil。
✅ 正确写法是让变量为值类型,再取其地址:
func sendFoo(c *gin.Context) {
var json foos // ← json 是 foos 值类型(非指针)
if err := c.BindJSON(&json); err != nil { // ← &json 是 *foos(合法的一级指针)
c.AbortWithStatusJSON(400, gin.H{"error": "invalid request", "details": err.Error()})
return
}
// ✅ 此时 binding:"required" 生效:{"bar":"bar bar"} 会返回 400
fmt.Println("Received foo:", json.Foo)
}
⚠️ 补充说明与最佳实践:
- 不要使用
var json *foos+BindJSON(&json)—— 这是常见误区; - 若需处理可选字段或零值敏感逻辑,可保留结构体指针语义,但绑定时仍应使用值类型变量(
foos{}),后续按需转为指针; - Gin 默认使用
json.Unmarshal解析原始字节,再调用校验器;binding标签仅在校验阶段生效,不参与反序列化本身; - 推荐统一使用
c.ShouldBindJSON()替代c.BindJSON(),前者在失败时不自动中止,便于自定义错误响应; - 示例完整路由注册:
r := gin.Default()
r.POST("/foo", sendFoo)
r.Run(":8080")
总结:Gin 的绑定校验机制严格区分类型层级,“只认 struct 和 struct,不认 struct”。牢记 BindJSON 的参数必须是 `YourStruct类型,而非YourStruct`——这是避免静默跳过验证的关键。











