Skip to content

嵌套结构体与切片

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 {
        // ...
    }
}

基于 MIT 协议发布。