自定义字段名称的显示
RegisterTagNameFunc
,自定义字段名称的显示,以便于从字段标签(tag
)中提取更有意义的名称。
代码示例:自定义字段名称
package mainimport ("fmt""reflect""strings""github.com/go-playground/validator/v10"
)type User struct {FirstName string `json:"first_name" fld:"First Name"`LastName string `json:"last_name" fld:"Last Name"`Age uint8 `json:"age" v:"gte=0,lte=130" fld:"Age"`Email string `json:"email" v:"required,email" fld:"Email Address"`
}func main() {validate := validator.New()// 注册字段名称的自定义提取函数validate.RegisterTagNameFunc(func(field reflect.StructField) string {name := strings.SplitN(field.Tag.Get("fld"), ",", 2)[0]if name == "-" {return ""}return name})user := &User{FirstName: "",LastName: "",Age: 150,Email: "invalid-email",}err := validate.Struct(user)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}
输出结果
Field: First Name, Error: Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag
Field: Last Name, Error: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag
Field: Age, Error: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Field: Email Address, Error: Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag
自定义结构体级别验证
结构体级别验证允许定义复杂的跨字段验证逻辑。例如,我们可以验证 FirstName
和 LastName
至少需要一个非空。
代码示例:结构体级别验证
func StructLevelValidation(sl validator.StructLevel) {user := sl.Current().Interface().(User)if user.FirstName == "" && user.LastName == "" {sl.ReportError(user.FirstName, "FirstName", "FirstName", "requiredtogether", "")sl.ReportError(user.LastName, "LastName", "LastName", "requiredtogether", "")}
}func main() {validate := validator.New()// 注册结构体级别验证validate.RegisterStructValidation(StructLevelValidation, User)user := &User{FirstName: "",LastName: "",Age: 25,Email: "valid@example.com",}err := validate.Struct(user)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}
输出结果
Field: FirstName, Error: Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'requiredtogether' tag
Field: LastName, Error: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'requiredtogether' tag
自定义验证函数
自定义验证函数允许我们定义更复杂的字段级别验证规则。例如,验证一个字符串是否是合法的 UUID 格式。
代码示例:字段级别自定义验证
func isUUID(fl validator.FieldLevel) bool {value := fl.Field().String()return len(value) == 36 // 简单模拟:UUID 长度为 36 个字符
}func main() {validate := validator.New()// 注册自定义验证函数validate.RegisterValidation("uuid", isUUID)type Resource struct {ID string `v:"required,uuid"`}resource := &Resource{ID: "invalid-uuid",}err := validate.Struct(resource)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}
输出结果
Field: ID, Error: Key: 'Resource.ID' Error:Field validation for 'ID' failed on the 'uuid' tag
表格汇总
功能 | 用法示例 | 说明 |
---|---|---|
自定义字段名称 | validate.RegisterTagNameFunc() | 从标签中提取更友好的字段名,如 fld:"First Name" |
结构体级别验证 | validate.RegisterStructValidation(func, StructType) | 在结构体级别实现复杂跨字段逻辑 |
自定义字段验证 | validate.RegisterValidation("tag", func) | 自定义验证规则(如检查 UUID 格式) |
错误报告 | sl.ReportError(field, name, structField, tag, param) | 在结构体验证中手动标记验证错误 |
补充
- errors.As()的错误处理
- ReportError进一步理解
struct_level.go
package validateimport ("errors""fmt""github.com/go-playground/validator/v10""reflect""strings"
)type StructLevelUser struct {FirstName stringLastName stringAge uint8 `v:"gte=0,lte=130"`Email string `v:"required,email" fld:"e-email"`
}func StructLevel() {// 注册一个自定义的标签名称函数,决定了如何从结构体字段的标签中提取验证规则和显示名称。// 接受一个函数作为参数,该函数接收一个 reflect.StructField 类型的字段,// 并返回一个字符串,表示该字段的标签名称。validate.RegisterTagNameFunc(func(field reflect.StructField) string {name := strings.SplitN(field.Tag.Get("fld"), ",", 2)[0]/*如果 fld:"e-email,some-other-data",strings.SplitN会将其分为两部分:e-email 和 some-other-data,取第一部分即 e-emailname == "-" 的意义Go 的惯例中,标签值 "-" 通常用来表示“忽略”某个字段。例如:在 JSON 序列化时,json:"-" 表示跳过该字段。在这里,fld:"-" 也可以被约定为“忽略此字段的验证显示名称”。*/if name == "-" {return ""}return name})// 注册一个自定义的结构体级别的验证函数。// 它允许你在整个结构体层面进行更复杂的验证逻辑,而不仅仅是单个字段的验证。// 接受一个函数作为参数,该函数接收一个 validator.StructLevel 类型的参数。// StructLevel 提供了访问当前结构体实例的方法,以及报告错误的功能。validate.RegisterStructValidation(func(sl validator.StructLevel) {user := sl.Current().Interface().(StructLevelUser)if len(user.LastName) == 0 && len(user.FirstName) == 0 { // 如果 FirstName 和 LastName 至少要求一个非空// sl.ReportError 是 validator.StructLevel 提供的一个方法,用于在结构体级别的自定义验证中报告错误。/*fieldName验证错误时字段的自定义名称(如需要特定显示或与代码解耦)。一般与结构体字段名保持一致。structFieldName用于定位字段。应该与字段名完全匹配。tag无关紧要,是规定的处理标志自定义的标签,标识错误类型。例如,"required" 或 "fnamelname",可根据业务需求定义。param可选参数,表示验证规则的额外信息。通常用于补充说明或配置自定义逻辑(这里为空字符串)*/sl.ReportError(user.FirstName, "fname", "FirstName", "fnamelname", "")sl.ReportError(user.LastName, "lname", "LastName", "fnamelname", "")}}, StructLevelUser{})user := &StructLevelUser{FirstName: "n",LastName: "c",Age: 100,Email: "123nick@0voice.com",}err := validate.Struct(user)if err != nil {var InvalidValidationError *validator.InvalidValidationErrorif errors.As(err, &InvalidValidationError) {fmt.Println(err)return} else {for _, err := range err.(validator.ValidationErrors) {fmt.Println(err)}return}}
}
struct.go
package validateimport ("errors""fmt""github.com/go-playground/validator/v10"
)type User struct { // validate.SetTagName("v")在这里,结构体的标签开头起作用Name string `v:"required,alphaunicode"`Age uint8 `v:"gte=10,lte=130"` // gte=10: 年龄必须大于等于 10。lte=130: 年龄必须小于等于 130。Phone string `v:"required,e164"` // e164: 电话号码必须符合 E.164 格式(国际电话号码格式)Email string `v:"required,email"`FavouriteColor1 string `v:"iscolor"` // 必须是一个有效的颜色值(例如:#ff0000)FavouriteColor2 string `v:"hexcolor|rgb|rgba|hsl|hsla"` // 必须是十六进制颜色、RGB、RGBA、HSL 或 HSLA 颜色格式之一Address *Address `v:"required"`ContactUser []*ContactUser `v:"required,gte=1,dive"` // dive: 对列表中的每个元素进行递归验证,后面如果继续跟逻辑运算,则处理对象是dive递归的元素Hobby []string `v:"required,gte=2,dive,required,gte=2,alphaunicode"` // dive: 对列表中的每个元素进行递归验证Data map[string]string `v:"required,gte=2,dive,keys,alpha,len=2,endkeys,required,gte=2,alphaunicode"` // keys,alpha,len=2: 键必须是字母且长度为 2。 endkeys: 结束对键的验证// required,gte=2,alphaunicode 值不能为空且必须包含至少 2 个字符,且只能是字母、数字或 Unicode 字符
}type ContactUser struct {Name string `v:"required,alphaunicode"`Age uint8 `v:"gte=10,lte=130"` // omitempty,代表当字段为空,不会出现在输出中Phone string `v:"required_without_all=Email Address,omitempty,e164"` // required_without_all 无需全部,即 如果 Phone 和 Email 都为空,则 Phone 和 Email 都必须填写,否则,Phone 可以为空。Email string `v:"required_without_all=Phone Address,omitempty,email"` // 如果 Phone 和 Address 字段都为空,则 Email 必须存在;否则,Email 可以为空。 email 必须为emailAddress *Address `v:"required_without_all=Phone Email"`
}type Address struct {Province string `v:"required"`City string `v:"required"`
}func StructValidate() {addr := &Address{Province: "湖南",City: "长沙",}contactUser1 := &ContactUser{Name: "nick",Age: 18,Phone: "+861380013800",}contactUser2 := &ContactUser{Name: "张三",Age: 18,Email: "nick@0voice.com",}user := &User{Name: "nick",Age: 18,Phone: "+861380013800",Email: "nick@0voice.com",FavouriteColor1: "#ffff",FavouriteColor2: "rgb(255,255,255)",Address: addr,ContactUser: []*ContactUser{contactUser1, contactUser2},Hobby: []string{"篮球", "羽毛球"},Data: map[string]string{"AB": "篮球", "CD": "羽毛球"},}err := validate.Struct(user)if err != nil {//if errors, ok := err.(validator.ValidationErrors); ok {// 错误的原因在于 validate.Struct(user) 返回的错误并不是直接的//validator.ValidationErrors 类型,而是可能被包装在其他类型的错误中。//因此,直接进行类型断言会失败。// 使用 errors.As 函数来检查是否包含 validator.ValidationErrors 类型的错误。var validationErrors validator.ValidationErrorsif errors.As(err, &validationErrors) { // errors.As 函数的第二个参数需要是一个指针,指向一个可以接收错误类型的变量。// 因此,一定要先var一个可以接受的,然后再使用errors.As,而不是直接if errors.As(err, &validator.ValidationErrors)for _, err := range validationErrors {fmt.Println(err)}} else {fmt.Println("Validation failed with unknown error:", err)}}fmt.Println("All validations passed!")
}
https://github.com/0voice