Go validator默认不递归验证嵌套结构体,需在字段tag中添加“dive”(如validate:"required,dive");切片用dive即可;structonly会跳过子字段;语言码校验宜用自定义lang-code而非oneof;nil指针需先判空再验证;中文提示须通过translator注册,不可写入tag。

嵌套结构体验证时 Validate 不触发子字段校验
默认情况下,Go 的 validator(如 github.com/go-playground/validator/v10)不会自动递归验证嵌套结构体字段。哪怕你给子结构体字段加了 validate:"required",只要没显式启用嵌套校验,Validate 就只检查顶层字段。
解决方法很简单:在嵌套字段的 struct tag 里加上 validate:"required,struct"(注意不是 structonly),或者更常用的是直接用 validate:"required" + validate:"dive" 组合:
type User struct {
Name string `validate:"required"`
Profile *Profile `validate:"required,dive"` // ← 关键:dive 触发递归验证
}
type Profile struct {
Age int `validate:"gte=0,lte=150"`
Lang string `validate:"oneof=zh en ja ko"`
}
-
dive是必须的,否则Profile字段即使为 nil 或字段违法也不会报错 - 如果字段是切片(如
[]Profile),则用validate:"dive"即可,无需额外required(除非你明确要求非空) -
structtag 值不写dive而写structonly会导致子字段完全跳过验证——这是常见误配
Lang 字段语言代码校验需兼顾 IETF BCP 47 与实际学习场景
单纯用 oneof=zh en ja ko 太窄,也不符合真实语言学习数据规范(比如用户可能填 zh-CN、en-GB、ja-JP)。但 validator 默认 oneof 不支持带连字符的枚举。
推荐做法是自定义校验函数,注册到 validator 实例:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
v := validator.New()
v.RegisterValidation("lang-code", func(fl validator.FieldLevel) bool {
s := fl.Field().String()
switch s {
case "zh", "zh-CN", "zh-TW", "en", "en-US", "en-GB",
"ja", "ja-JP", "ko", "ko-KR", "es", "fr", "de":
return true
}
return false
})
- 调用时写
validate:"lang-code",比硬编码oneof更易维护 - 避免用正则(如
regexp=^[a-z]{2}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$)——BCP 47 实际规则远比这复杂,且 validator 的 regexp 性能较差 - 若需支持动态语言列表(如从 DB 加载),可在校验函数中查 map,但注意并发安全(建议初始化时构建只读 map)
零值嵌套指针字段导致 ValidationError 信息丢失
当嵌套字段是 *Profile 类型且为 nil,而 tag 写的是 validate:"required,dive",validator 会直接 panic 或返回空错误(取决于版本),而不是告诉你 “Profile is nil”。这是 v10.14+ 的已知行为变化。
- 安全写法是拆成两步:
validate:"required"确保非 nil,再用dive验证内部字段 - 或者改用
validate:"omitempty,dive"——但此时Profile为 nil 会被跳过,无法捕获 “本该提供但没提供” 的业务逻辑错误 - 更稳妥的做法:对关键嵌套指针字段,先手动判空,再调
Struct验证子结构体,这样错误位置和消息都可控
中文错误提示需替换 translate 而非修改 tag
validator 自带的英文错误信息(如 Key: 'User.Profile.Lang' Error:Field validation for 'Lang' failed on the 'oneof' tag)对终端用户不友好。但别试图在 struct tag 里写中文(如 validate:"required=姓名必填"),这违反 tag 设计语义,且无法被 validator 解析。
正确路径是使用 ut.Translator + en_US/zh_CN 语言包:
trans, _ := ut.New(uni, uni).GetTranslator("zh_CN")
v.RegisterTranslation("required", trans, func(ut ut.Translator) error {
return ut.Add("required", "{0} 不能为空", true)
})
// 然后传给 v.StructCtx(ctx, obj, trans)
- 必须用
StructCtx并传入 translator,仅Struct不生效 - 字段名翻译(如把
Lang显示为 “语言代码”)需额外调RegisterFieldName,否则占位符{0}还是字段名 - 第三方中文翻译包(如 go-playground/validator/v10/translations/zh)更新滞后,建议自己维护核心字段映射
多级嵌套验证真正麻烦的不是语法,而是错误路径的可读性——User.Profile.Lang 这种路径对前端或日志系统不友好,需要在 translator 里做字段扁平化映射,这点容易被忽略。

















