
Go 里没有“自动映射 + 深度校验”开箱即用的魔法,必须手动组合 json.Unmarshal(或框架 ShouldBind)与 validator.Struct(),且结构体字段必须首字母大写、嵌套需加 dive、指针字段要小心初始化——否则校验静默失效。
结构体字段小写导致校验完全不触发
validator 依赖反射读取字段值,只访问导出字段(即首字母大写)。小写字段哪怕 tag 写得再全,也会被跳过:不报错、不校验、不提醒。
- 错误写法:
type User { name string `validate:"required"` }→name不参与任何校验 - 正确写法:
type User { Name string `json:"name" validate:"required"` } -
json:"name"只控制序列化键名,不影响字段是否导出;字段名本身必须大写才能被 validator 看见 - 嵌套结构体同理:内层
Child的字段也必须大写,否则Child.Name不会被检查
嵌套结构体校验失效?检查 dive 和指针初始化
validator 默认不会递归校验嵌套结构体字段,除非显式声明 dive;同时,对非 nil 指针会强制进入子校验,哪怕它指向全零值对象。
- 没加
dive:字段Address Address的内部字段(如Address.Street)不会被校验 - 正确写法:
Address Address `validate:"dive"`或Address *Address `validate:"omitempty,dive"` -
omitempty只对指针/接口/map/slice/chan/func 有效;普通 struct 字段即使为空也会进子校验 - 若字段是
*Address类型,传nil就跳过;但若传了非 nil 指针(如&Address{}),子字段仍会校验 —— 所以反序列化后务必确认指针状态
Gin 中 ShouldBind 不等于自动校验完成
Gin 的 c.ShouldBind(&req) 默认只做类型转换和基础 binding 校验(如 binding:"required"),它底层调用的是 Gin 自带的 validator 实例。若你用了 go-playground/validator/v10 的高级规则(如 email、min=2、自定义函数),必须手动替换 Gin 的 validator 引擎。
- 默认行为:Gin v1.9+ 内置的是 validator/v9 兼容层,
validate:"email"可能静默忽略 - 必须步骤:创建
v := validator.New(),注册自定义规则,再执行engine.Validator = &defaultValidator{validator: v} - 别混用:
Validate.Var()只校验单个值,Validate.Struct()才走完整标签解析和dive逻辑 - 错误现象:结构体 tag 写了
validate:"required",但ShouldBind返回 nil —— 很可能 validator 实例没换,或字段未导出
query/path 参数不能直接走结构体校验
URL 查询参数(?page=1&size=10)和路径参数(/users/:id)是字符串,不会自动绑定到结构体字段,也不能直接用 validator.Struct() 校验。
- query 参数:用
r.URL.Query().Get("page")取值,再用strconv.ParseInt转换并检查err != nil;避免直接strconv.Atoi导致 panic - path 参数:如
chi.URLParam(r, "id")返回字符串,必须手动转类型 + 业务校验(如 ID > 0) - 若想复用 validator,可封装成独立结构体,用
mapstructure.Decode或手写映射,再调用Validate.Struct() - 别信任默认值:比如
page设默认为 1,但若前端传page=(空字符串),应视为非法而非 fallback
最常被忽略的点是:校验不是“写完 tag 就结束”,而是从字段命名、结构体初始化、框架 validator 替换、到 query/path 的手动转换,每个环节断掉都会让整个校验链失效——尤其指针字段在反序列化后处于 “非 nil 但全零” 状态,最容易漏判。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











