validator Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving

Validator包是Golang中一个非常受欢迎的数据验证工具,它提供了丰富的验证规则和简单易用的API。使用Validator包可以轻松地定义和执行各种验证规则,如必填字段、最大长度、最小值等。同时,Validator包还支持自定义验证规则,可以根据具体的业务需求进行扩展。

validator 是一个开源的验证器包,可以快速校验输入信息是否符合自定规则。

在web开发中校验请求参数尤为重要,gin框架中使用的就是validator包来校验参数,

只需要在参数结构体中添加binding tag,然后使用Bind系列方法解析参数即可。

安装

1
go get -u github.com/go-playground/validator/v10

使用包

1
import "github.com/go-playground/validator/v10"

使用示例: https://github.com/go-playground/validator/tree/master/_examples

使用

定义一个结构体来表示待验证的数据:

1
2
3
4
5
6
type User struct {
    Name     string `validate:"required"`
    Age      int    `validate:"gte=0,lte=100"`
    Email    string `validate:"email"`
    Password string `validate:"required,min=6"`
}

在结构体的字段上使用validate标签来定义验证规则。上述示例中,Name字段是必填字段,Age字段的值必须大于等于0且小于等于100,Email字段的值必须符合邮箱格式,Password字段是必填字段且长度必须大于等于6。

然后,需要创建一个Validator实例:

1
validate := validator.New()

接下来,可以使用Validator实例的Struct方法来验证结构体的数据:

1
2
3
4
5
6
7
8
9
user := User{Name: "John", Age: 25, Email: "john@example.com", Password: "password"}
err := validate.Struct(user)
if err != nil {
    // 处理验证失败的情况
    fmt.Println(err)
} else {
    // 处理验证成功的情况
    fmt.Println("Validation passed")
}

在验证过程中,如果验证失败,Validator包会返回一个ValidationError对象,其中包含了验证失败的详细信息。可以根据具体的需求来处理验证失败的情况,比如输出错误信息或者进行其他操作。

字符串约束

  • excludesall:不包含参数中任意的 UNICODE 字符,例如excludesall=ab;
  • excludesrune:不包含参数表示的 rune 字符,excludesrune=asong;
  • startswith:以参数子串为前缀,例如startswith=hi;
  • endswith:以参数子串为后缀,例如endswith=bye。
  • contains=:包含参数子串,例如contains=email;
  • containsany:包含参数中任意的 UNICODE 字符,例如containsany=ab;
  • containsrune:包含参数表示的 rune 字符,例如`containsrune=asong;
  • excludes:不包含参数子串,例如excludes=email;

范围约束

范围约束的字段类型分为三种:

  • 对于数值,我们则可以约束其值
  • 对于切片、数组和map,我们则可以约束其长度
  • 对于字符串,我们则可以约束其长度

常用tag介绍:

  • ne:不等于参数值,例如ne=5;
  • gt:大于参数值,例如gt=5;
  • gte:大于等于参数值,例如gte=50;
  • lt:小于参数值,例如lt=50;
  • lte:小于等于参数值,例如lte=50;
  • oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,例如oneof=male female。
  • eq:等于参数值,注意与len不同。对于字符串,eq约束字符串本身的值,而len约束字符串长度。例如eq=10;
  • len:等于参数值,例如len=10;
  • max:小于等于参数值,例如max=10;
  • min:大于等于参数值,例如min=10

Fields约束

  • eqfield:定义字段间的相等约束,用于约束同一结构体中的字段。例如:eqfield=Password
  • eqcsfield:约束统一结构体中字段等于另一个字段(相对),确认密码时可以使用,例如:eqfiel=ConfirmPassword
  • nefield:用来约束两个字段是否相同,确认两种颜色是否一致时可以使用,例如:nefield=Color1
  • necsfield:约束两个字段是否相同(相对)

常用约束

  • unique:指定唯一性约束,不同类型处理不同:

    • 对于map,unique约束没有重复的值
    • 对于数组和切片,unique没有重复的值
    • 对于元素类型为结构体的碎片,unique约束结构体对象的某个字段不重复,使用unique=field指定字段名
  • email:使用email来限制字段必须是邮件形式,直接写eamil即可,无需加任何指定。

  • omitempty:字段未设置,则忽略

  • -:跳过该字段,不检验;

  • |:使用多个约束,只需要满足其中一个,例如rgb|rgba;

  • required:字段必须设置,不能为默认值;

标记 标记说明
required 必填 Field或Struct validate:“required”
omitempty 空时忽略 Field或Struct validate:“omitempty”
len 长度 Field validate:“len=0”
eq 等于 Field validate:“eq=0”
gt 大于 Field validate:“gt=0”
gte 大于等于 Field validate:“gte=0”
lt 小于 Field validate:“lt=0”
lte 小于等于 Field validate:“lte=0”
eqfield 同一结构体字段相等 Field validate:“eqfield=Field2”
nefield 同一结构体字段不相等 Field validate:“nefield=Field2”
gtfield 大于同一结构体字段 Field validate:“gtfield=Field2”
gtefield 大于等于同一结构体字段 Field validate:“gtefield=Field2”
ltfield 小于同一结构体字段 Field validate:“ltfield=Field2”
ltefield 小于等于同一结构体字段 Field validate:“ltefield=Field2”
eqcsfield 跨不同结构体字段相等 Struct1.Field validate:“eqcsfield=Struct2.Field2”
necsfield 跨不同结构体字段不相等 Struct1.Field validate:“necsfield=Struct2.Field2”
gtcsfield 大于跨不同结构体字段 Struct1.Field validate:“gtcsfield=Struct2.Field2”
gtecsfield 大于等于跨不同结构体字段 Struct1.Field validate:“gtecsfield=Struct2.Field2”
ltcsfield 小于跨不同结构体字段 Struct1.Field validate:“ltcsfield=Struct2.Field2”
ltecsfield 小于等于跨不同结构体字段 Struct1.Field validate:“ltecsfield=Struct2.Field2”
min 最大值 Field validate:“min=1”
max 最小值 Field validate:“max=2”
structonly 仅验证结构体,不验证任何结构体字段 Struct validate:“structonly”
nostructlevel 不运行任何结构级别的验证 Struct validate:“nostructlevel”
dive 向下延伸验证,多层向下需要多个dive标记 [][]string validate:“gt=0,dive,len=1,dive,required”
dive Keys & EndKeys 与dive同时使用,用于对map对象的键的和值的验证,keys为键,endkeys为值 map[string]string validate:“gt=0,dive,keys,eq=1
required_with 其他字段其中一个不为空且当前字段不为空 Field validate:“required_with=Field1 Field2”
required_with_all 其他所有字段不为空且当前字段不为空 Field validate:“required_with_all=Field1 Field2”
required_without 其他字段其中一个为空且当前字段不为空 Field `validate:“required_without=Field1 Field2” |
required_without_all 其他所有字段为空且当前字段不为空 Field validate:“required_without_all=Field1 Field2”
isdefault 是默认值 Field validate:“isdefault=0”
oneof 其中之一 Field validate:“oneof=5 7 9”
containsfield 字段包含另一个字段 Field validate:“containsfield=Field2”
excludesfield 字段不包含另一个字段 Field validate:“excludesfield=Field2”
unique 是否唯一,通常用于切片或结构体 Field validate:“unique”
alphanum 字符串值是否只包含 ASCII 字母数字字符 Field validate:“alphanum”
alphaunicode 字符串值是否只包含 unicode 字符 Field validate:“alphaunicode”
alphanumunicode 字符串值是否只包含 unicode 字母数字字符 Field validate:“alphanumunicode”
numeric 字符串值是否包含基本的数值 Field validate:“numeric”
hexadecimal 字符串值是否包含有效的十六进制 Field validate:“hexadecimal”
hexcolor 字符串值是否包含有效的十六进制颜色 Field validate:“hexcolor”
lowercase 符串值是否只包含小写字符 Field validate:“lowercase”
uppercase 符串值是否只包含大写字符 Field validate:“uppercase”
email 字符串值包含一个有效的电子邮件 Field validate:“email”
json 字符串值是否为有效的 JSON Field validate:“json”
file 符串值是否包含有效的文件路径,以及该文件是否存在于计算机上 Field validate:“file”
url 符串值是否包含有效的 url Field validate:“url”
uri 符串值是否包含有效的 uri Field validate:“uri”
base64 字符串值是否包含有效的 base64值 Field validate:“base64”
contains 字符串值包含子字符串值 Field validate:“contains=@”
containsany 字符串值包含子字符串值中的任何字符 Field validate:“containsany=abc”
containsrune 字符串值包含提供的特殊符号值 Field validate:“containsrune=☢”
excludes 字符串值不包含子字符串值 Field validate:“excludes=@”
excludesall 字符串值不包含任何子字符串值 Field validate:“excludesall=abc”
excludesrune 字符串值不包含提供的特殊符号值 Field validate:“containsrune=☢”
startswith 字符串以提供的字符串值开始 Field validate:“startswith=abc”
endswith 字符串以提供的字符串值结束 Field validate:“endswith=abc”
ip 字符串值是否包含有效的 IP 地址 Field validate:“ip”
ipv4 字符串值是否包含有效的 ipv4地址 Field validate:“ipv4”
datetime 字符串值是否包含有效的 日期 Field validate:"datetime

单字段校验

单字段多个条件 校验

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
 "fmt"
 "strings"

 "github.com/go-playground/locales/en"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"

 zhtrans "github.com/go-playground/validator/v10/translations/zh"
 // entrans "github.com/go-playground/validator/v10/translations/en"
)

/*
https://www.cnblogs.com/jiujuan/p/13823864.html

https://www.liwenzhou.com/posts/Go/validator-usages/

https://juejin.cn/post/7056823502640250893

https://juejin.cn/post/6847902214279659533
*/
type Student struct {
 Name  string `validate:required`
 Email string `validate:"email"`
 Age   int    `validate:"max=30,min=12"`
}

func main() {
 en := en.New() //英文翻译器
 zh := zh.New() //中文翻译器

 // 第一个参数是必填,如果没有其他的语言设置,就用这第一个
 // 后面的参数是支持多语言环境(
 // uni := ut.New(en, en) 也是可以的
 // uni := ut.New(en, zh, tw)
 uni := ut.New(en, zh)
 trans, _ := uni.GetTranslator("zh") //获取需要的语言

 student := Student{
  Name:  "tom",
  Email: "testemal",
  Age:   40,
 }
 validate := validator.New()

 zhtrans.RegisterDefaultTranslations(validate, trans)

 err := validate.Struct(student)
 if err != nil {
  // fmt.Println(err)

  errs := err.(validator.ValidationErrors)
  fmt.Println(removeStructName(errs.Translate(trans)))
 }
}

func removeStructName(fields map[string]string) map[string]string {
 result := map[string]string{}

 for field, err := range fields {
  result[field[strings.Index(field, ".")+1:]] = err
 }
 return result
}

gin使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Signup struct {
    Email string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required"`
    ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=ConfirmPassword"`
}

func main() {
	r := gin.Default()
	r.POST("/signup", func(c *gin.Context) {
		var u Signup
		if err := c.ShouldBind(&u); err != nil {
            ...

Usage and documentation

bind使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main
 
import (
    "fmt"
    "github.com/go-playground/validator/v10"
)
 
type User struct {
    Username string `json:"username" validate:"required"`
    Age      uint8  `json:"age" validate:"gte=0,lte=120"`
    Email    string `json:"email" validate:"required,email"`
}
 
func main() {
    validate := validator.New(validator.WithRequiredStructEnabled())
 
    user := User{
        Username: "john.doe",
        Age:      121, // 不合法的年龄
        Email:    "not-an-email", // 不合法的邮箱
    }
 
    err := validate.Struct(user)
    if err != nil {
        // 打印验证错误
        if _, ok := err.(*validator.InvalidValidationError); ok {
            fmt.Println(err)
            return
        }
 
        for _, err := range err.(validator.ValidationErrors) {
            fmt.Println(err.Namespace())
            fmt.Println(err.Field())
            fmt.Println(err.StructNamespace())
            fmt.Println(err.StructField())
            fmt.Println(err.Tag())
            fmt.Println(err.ActualTag())
            fmt.Println(err.Kind())
            fmt.Println(err.Type())
            fmt.Println(err.Value())
            fmt.Println(err.Param())
        }
    }
}

自定义验证器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
	"reflect"

	"github.com/go-playground/validator/v10"
)

// DbBackedUser User struct
type DbBackedUser struct {
	Name sql.NullString `validate:"required"`
	Age  sql.NullInt64  `validate:"required"`
}

// use a single instance of Validate, it caches struct info
var validate *validator.Validate

func main() {

	validate = validator.New()

	// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
	validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})

	// build object for validation
	x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}

	err := validate.Struct(x)

	if err != nil {
		fmt.Printf("Err(s):\n%+v\n", err)
	}
}

// ValidateValuer implements validator.CustomTypeFunc
func ValidateValuer(field reflect.Value) interface{} {

	if valuer, ok := field.Interface().(driver.Valuer); ok {

		val, err := valuer.Value()
		if err == nil {
			return val
		}
		// handle the error how you want
	}

	return nil
}

or

1
2
3
4
5
6
7
// 注册自定义标签
_ = validate.RegisterValidation("custom-tag-name", ValidatePre)

// 自定义验证规则
func ValidatePre(fl validator.FieldLevel) bool {
	return fl.Field().String() == "custom-value"
}

参考