Skip to content

Nested Structs & Slices

Check walks the entire field tree automatically. You don't have to opt in or call anything special to validate nested data.

Plain nested structs

Every exported field is visited recursively, and tags fire just like top-level fields:

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 // no tag needed; fields inside are still validated
}

Slices of structs

Each element is validated independently. Errors come back in declaration order, then per-element order:

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不能为空"  (from element 1)
// errs[1]: "数量应大于1"   (from element 1)

The outer field's required rule still applies, so an empty Items: nil slice yields its own 购物车不能为空-style error.

Embedded (anonymous) structs

Embedded structs are walked just like named fields. This works even when the embedded type itself is unexported — the validator descends into the type's exported fields without panicking on the parent's Interface() call:

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]: "其他不能为空"

Pointers to structs

Pointer fields are dereferenced automatically. Nil pointers are safe — you'll get Cfg不能为空 from required rather than a panic:

go
type Config struct {
    Host string `valid:"required" label:"主机"`
}

type Service struct {
    Cfg *Config `valid:"required" label:"配置"`
}

Slices of pointers to structs

Slices of *T aren't recursed into element-by-element today — only the outer slice's own rules fire. If you need per-element validation, use []T instead, or run a manual loop:

go
for i, item := range pointers {
    if errs, ok := govalid.Check(item); !ok {
        // ...
    }
}

Validating top-level slices

Check accepts a slice of structs at the top level too. Every element is validated:

go
items := []Item{{SKU: "A1", Qty: 1}, {SKU: "", Qty: 0}}
errs, ok := govalid.Check(items)

Validating maps and channels

Only structs and slices-of-structs are considered "validatable containers". Maps, channels and primitives at the top level return (nil, true) — the validator silently passes them. To validate map values that are structs, iterate manually:

go
for _, v := range users {
    if errs, ok := govalid.Check(v); !ok {
        // ...
    }
}

Released under the MIT License.