标签用法总结表
标签 | 功能 | 代码实例 |
---|---|---|
required |
字段必填 | Name string \ v:"required"`` |
alphaunicode |
验证字段是否只包含字母和 Unicode 字符 | Name string \ v:"alphaunicode"`` |
gte |
验证字段值是否大于等于指定值 | Age uint8 \ v:"gte=10"`` |
lte |
验证字段值是否小于等于指定值 | Age uint8 \ v:"lte=130"`` |
e164 |
验证电话号码是否符合 E.164 国际格式 | Phone string \ v:"e164"`` |
email |
验证字段是否为有效的电子邮件地址 | Email string \ v:"email"`` |
iscolor |
验证字段是否为有效的颜色值(如 #ff0000 ) |
FavouriteColor1 string \ v:"iscolor"`` |
hexcolor |
验证字段是否为十六进制颜色值(如 #ffffff ) |
FavouriteColor2 string \ v:"hexcolor"`` |
`rgb | rgba | hsl |
dive |
对切片或 map 的每个元素进行递归验证 | Hobby []string \ v:"dive,required"`` |
keys |
验证 map 的键值 | Data map[string]string \ v:"dive,keys,alpha,len=2,endkeys"`` |
required_without_all |
如果指定字段都为空,则当前字段必须存在 | Phone string \ v:"required_without_all=Email Address,omitempty"`` |
omitempty |
当字段为空时,跳过后续验证 | Age uint8 \ v:"omitempty,gte=10,lte=130"`` |
len |
验证字段的长度是否等于指定值 | Data map[string]string \ v:"dive,keys,alpha,len=2,endkeys"\ `` |
endkeys |
结束对 map 键的验证 | Data map[string]string \ v:"dive,keys,alpha,len=2,endkeys,required"`` |
标签参考:https://github.com/go-playground/validator?tab=readme-ov-file
示例代码
go
package validate
import (
"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 必须为email
Address *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.ValidationErrors
if 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)
}
}
}
示例 1: 必填字段验证
go
Name string `v:"required,alphaunicode"`
required
:Name
字段不能为空。alphaunicode
:Name
只能包含字母或 Unicode 字符。
示例 2: 数值范围验证
go
Age uint8 `v:"gte=10,lte=130"`
gte=10
:Age
必须大于等于 10。lte=130
:Age
必须小于等于 130。
示例 3: 列表递归验证
go
Hobby []string `v:"required,gte=2,dive,required,gte=2,alphaunicode"`
required
:Hobby
列表不能为空。gte=2
:Hobby
列表必须包含至少 2 个元素。dive
:对列表中的每个元素进行递归验证。required,gte=2,alphaunicode
:列表中的每个元素必须非空,且至少为 2 个字符,且只包含字母和 Unicode 字符。
示例 4: Map 验证
go
Data map[string]string `v:"required,gte=2,dive,keys,alpha,len=2,endkeys,required,gte=2,alphaunicode"`
required,gte=2
:Data
必须有至少 2 个键值对。dive
:递归验证 map 的每个键和值。keys,alpha,len=2,endkeys
:键必须为字母,且长度为 2。required,gte=2,alphaunicode
:值不能为空,至少为 2 个字符,且只能包含字母和 Unicode 字符。
Go 语言中 omitempty
和 required_without_all
的合理使用
1. omitempty
标签的作用
omitempty
是 json
标签的一个选项,用于控制 JSON 序列化时的行为。当字段的值为零值(如空字符串、0、nil 等)时,该字段将被忽略,不会出现在生成的 JSON 输出中。
示例:
go
type User struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
}
- 如果
Name
或Age
字段为空或为零值,则它们不会出现在 JSON 输出中。
go
user := User{
Name: "Alice",
Age: 0,
}
jsonData, _ := json.Marshal(user)
fmt.Println(string(jsonData)) // 输出: {"name":"Alice"}
2. required_without_all
标签的作用
required_without_all
是 validator
标签的一部分,用于条件验证。它表示当指定的其他字段都为空时,当前字段必须存在且不为空。
示例:
go
type ContactUser struct {
Phone string `v:"required_without_all=Email Address,e164"`
Email string `v:"required_without_all=Phone Address,email"`
Address *Address `v:"required_without_all=Phone Email"`
}
- 如果
Phone
和Email
都为空,则Phone
必须存在且符合 E.164 格式。 - 如果
Phone
和Address
都为空,则Email
必须存在且是有效的电子邮件地址。 - 如果
Phone
和Email
字段有一个非空,则另一个字段可以为空。
3. omitempty
和 required_without_all
的组合使用
这两个标签可以共存,但需要注意它们分别作用于不同的阶段:
required_without_all
用于数据验证,确保在特定条件下字段不为空。omitempty
用于 JSON 序列化,确保零值字段不会出现在输出中。
示例:
go
type ContactUser struct {
Name string `json:"name,omitempty" v:"required,alphaunicode"`
Age uint8 `json:"age,omitempty" v:"gte=10,lte=130"`
Phone string `json:"phone,omitempty" v:"required_without_all=Email Address,e164"`
Email string `json:"email,omitempty" v:"required_without_all=Phone Address,email"`
Address *Address `json:"address,omitempty" v:"required_without_all=Phone Email"`
}
- 验证逻辑 :
required_without_all
确保在特定条件下字段不为空。 - JSON 序列化 :
omitempty
确保在 JSON 序列化时,零值字段不会出现在输出中。
4. 最佳实践建议
为了提高代码的可读性和维护性,建议将 json
标签和 validator
标签分开写:
go
type ContactUser struct {
Name string `json:"name,omitempty" v:"required,alphaunicode"`
Age uint8 `json:"age,omitempty" v:"gte=10,lte=130"`
Phone string `json:"phone,omitempty" v:"required_without_all=Email Address,e164"`
Email string `json:"email,omitempty" v:"required_without_all=Phone Address,email"`
Address *Address `json:"address,omitempty" v:"required_without_all=Phone Email"`
}
多层struct的go案例讲解
go
package main
import (
"errors"
"fmt"
"github.com/go-playground/validator/v10"
)
// 嵌套的地址结构
type Address struct {
Street string `validate:"required"` // 街道名称必填
City string `validate:"required,alpha"` // 城市名称必填,只能包含字母
ZipCode string `validate:"required,len=5"` // 邮编必填,且长度必须为 5
}
// 嵌套的联系人结构
type Contact struct {
Name string `validate:"required,alphaunicode"` // 名称必填,支持字母和 Unicode 字符
Phone string `validate:"required_without=Email,e164"` // 电话和邮箱至少填一个,且电话需符合 E.164 格式
Email string `validate:"required_without=Phone,email"` // 如果没有填写电话,邮箱必须是有效的邮件地址
Address Address `validate:"required,dive"` // 验证嵌套的地址字段
}
// 主用户结构
type User struct {
ID int `validate:"required,gte=1"` // 用户 ID 必填,且必须大于等于 1
Username string `validate:"required,alphanum"` // 用户名必填,只能包含字母和数字
Contacts []Contact `validate:"required,gte=1,dive"` // 至少包含一个联系人,每个联系人需通过验证
Hobbies []string `validate:"required,gte=1,dive,alphaunicode"` // 至少有一个爱好,每个爱好只能包含字母和 Unicode 字符
}
var validate = validator.New()
func main() {
// 构造测试数据
user := User{
ID: 101,
Username: "john_doe",
Contacts: []Contact{
{
Name: "Alice",
Phone: "+1234567890",
Address: Address{
Street: "123 Main St",
City: "Springfield",
ZipCode: "12345",
},
},
{
Name: "Bob",
Email: "bob@example.com",
Address: Address{
Street: "456 Elm St",
City: "Shelbyville",
ZipCode: "54321",
},
},
},
Hobbies: []string{"Reading", "Cooking"},
}
// 进行验证
err := validate.Struct(user)
if err != nil {
// 使用 errors.As 检查是否包含 ValidationErrors
var validationErrors validator.ValidationErrors // 底层是一个切片
if errors.As(err, &validationErrors) {
for _, fieldErr := range validationErrors {
fmt.Printf("Field '%s' failed validation: %s\n", fieldErr.Field(), fieldErr.Tag())
}
} else {
fmt.Println("Validation failed:", err)
}
} else {
fmt.Println("All validations passed!")
}
}
1. 嵌套结构
Address
和Contact
是嵌套结构:Address
包含Street
、City
和ZipCode
,验证逻辑应用到每个字段。Contact
包含Name
、Phone
、Email
和嵌套的Address
,同时对Phone
和Email
设置了互斥验证。
2. 多层级验证
- 一级验证 :
User
的ID
和Username
。 - 二级验证 :
User
的Contacts
列表: - 验证列表中至少包含一个联系人(
gte=1
)。 - 使用
dive
,递归验证每个联系人。 - 三级验证 :
Contact
中的Address
: - 验证
Street
、City
和ZipCode
。
3. 验证列表和递归验证
Contacts
使用dive
标签递归验证列表内的每个元素。- 对
Hobbies
列表的验证中: - 使用
dive
验证列表中的每个爱好,确保它们符合alphaunicode
的规则。
4. 动态互斥规则
Phone
和Email
互斥规则:Phone
:required_without=Email
,表示如果Email
为空,Phone
必须存在。Email
:required_without=Phone
,表示如果Phone
为空,Email
必须是有效邮件地址。