嵌套结构体与切片
Check 会自动遍历整个字段树。你不需要做任何额外开关或调用,嵌套 数据就会被校验。
普通嵌套结构体
每个导出字段都会被递归访问,标签的触发方式和顶层字段一致:
go
type Address struct {
Street string `valid:"required" label:"街道"`
Zip string `valid:"required;alphanumeric" label:"邮编"`
}
type Profile struct {
Name string `valid:"required" label:"姓名"`
Address Address // 不需要标签;其内部字段仍会被校验
}struct 切片
每个元素都会被独立校验。错误顺序按字段声明顺序,再按元素顺序:
go
type Item struct {
SKU string `valid:"required" label:"SKU"`
Qty int `valid:"min:1" label:"数量"`
}
type Order struct {
Items []Item `valid:"required" label:"商品"`
}
errs, _ := govalid.Check(Order{
Items: []Item{
{SKU: "A1", Qty: 5},
{SKU: "", Qty: 0},
},
})
// errs[0]: "SKU不能为空" (来自第 1 个元素)
// errs[1]: "数量应大于1" (来自第 1 个元素)外层字段的 required 规则也会生效,所以空 Items: nil 切片会单独 触发它自己的"商品不能为空"。
嵌入(匿名)结构体
嵌入 struct 会像具名字段一样被遍历。即使嵌入类型本身未导出也 能正常工作——校验器会下沉到该类型的导出字段,不会因为父级的 Interface() 调用 panic:
go
type embeddedBase struct {
Name string `valid:"required" label:"姓名"`
}
type Form struct {
embeddedBase
Other string `valid:"required" label:"其他"`
}
errs, _ := govalid.Check(Form{})
// errs[0]: "姓名不能为空"
// errs[1]: "其他不能为空"struct 指针
指针字段会被自动解引用。空指针是安全的——你会从 required 得到 配置不能为空 而不是 panic:
go
type Config struct {
Host string `valid:"required" label:"主机"`
}
type Service struct {
Cfg *Config `valid:"required" label:"配置"`
}struct 指针切片
今天 []*T(指针切片)不会被逐个元素递归——只有外层切片自己的规则 会触发。如果需要逐元素校验,请改用 []T,或自己 loop 一遍:
go
for i, item := range pointers {
if errs, ok := govalid.Check(item); !ok {
// ...
}
}顶层切片校验
Check 也接受顶层的 struct 切片。每个元素都会被校验:
go
items := []Item{{SKU: "A1", Qty: 1}, {SKU: "", Qty: 0}}
errs, ok := govalid.Check(items)map 与 channel 校验
只有 struct 和 struct 切片被认为是"可校验容器"。顶层的 map、channel 和原始类型会返回 (nil, true)——校验器静默放行。如果想校验 map 值(当它们是 struct 时),手动迭代:
go
for _, v := range users {
if errs, ok := govalid.Check(v); !ok {
// ...
}
}