go-validator 是最主流的非官方结构体验证库,需通过 validate tag 配合反射使用,注意 required 对指针无效、嵌套字段须加 dive、错误需类型断言提取。

Go-validator(即 go-playground/validator)不是 Go 官方库,但它是目前最主流、最稳定的结构体字段验证方案;它不依赖 HTTP 框架,能直接嵌入任何 struct,但必须配合 struct tag 使用,且默认不校验嵌套指针或 nil 字段——这点最容易出错。
如何给 struct 字段加 validator tag
validator 通过反射读取 struct tag(如 validate:"required,email")工作,tag 名必须是 validate,不能写成 valid 或 validation。空格、逗号分隔规则,顺序无关,但部分规则有隐含依赖(比如 required 要放在 email 前面,否则 email 可能在字段为空时直接报错,掩盖了缺失问题)。
常见写法示例:
type UserForm struct {
Name string `validate:"required,min=2,max=20"`
Email string `validate:"required,email"`
Age int `validate:"required,gt=0,lt=150"`
Password string `validate:"required,min=8"`
}
-
required只检查零值(""、0、nil),对指针类型需额外处理 -
email不做 DNS 查询,只做基础格式校验(RFC 5322 子集) -
min/max对字符串是长度,对数字是数值大小 - 嵌套 struct 需加
validate:"dive"才会递归校验
如何触发验证并获取错误信息
验证入口是 Validate.Struct() 方法,返回 error 类型,但实际错误是 validator.ValidationErrors 接口,需类型断言才能提取字段级错误。直接用 err.Error() 只会输出一行摘要,无法定位具体字段。
典型用法:
v := validator.New()
err := v.Struct(form)
if err != nil {
if errs, ok := err.(validator.ValidationErrors); ok {
for _, e := range errs {
// e.Field() → 字段名,e.Tag() → 触发的 tag(如 "required"),e.Value() → 实际值
log.Printf("field %s failed %s: %v", e.Field(), e.Tag(), e.Value())
}
}
}
- 不要忽略
ok判断,非 validator 错误(如内存不足)也会走这里 -
ValidationErrors是 slice,可直接 range,每个元素带上下文,比手动解析字符串可靠得多 - 若用 Gin 等框架,其 binding 已集成 validator,但默认错误格式简陋,建议仍手动调用
Validate.Struct()控制输出
如何处理指针字段和嵌套结构体
struct 中字段为指针(如 *string)时,required 默认不生效——因为指针本身非 nil 就算“有值”,哪怕它指向空字符串。此时要用 required_if 或自定义函数,更常用的是加 nonzero tag(表示指针非 nil 且解引用后非零值)。
嵌套 struct 默认不校验,必须显式加 dive:
type Address struct {
City string `validate:"required"`
Zip string `validate:"required,len=6"`
}
type UserForm struct {
Name string `validate:"required"`
Address Address `validate:"required,dive"` // ← 必须加 dive
}
-
dive只作用于当前字段,不会自动穿透到下一层指针,如*Address还需额外加nilable或先判空 - map/slice 元素校验也要
dive,例如[]UserForm→validate:"dive" - 自定义 error message 需提前注册
RegisterTranslation,否则返回英文
validator 的核心是 struct tag + 反射,它不关心你是从 JSON 解析还是从表单提交来的数据,但所有字段必须导出(首字母大写),且 tag 拼写、空格、引号都不能错;最常被卡住的其实是嵌套字段漏了 dive,或者指针字段误以为 required 能判 nil。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











