bigywater.tech
Gin · 31

validator 常用规则与错误信息

binding:"required,min=3"、oneof、email、dive……这些规则到底怎么判、报什么错、和字段类型有什么关系?这篇用一份全不合格的请求把常用规则跑一遍,逐个看 FieldError 里的信息,再看错误的三种类型和嵌套、切片的校验边界。

11 项
一份全不合格的请求里 validator 报出的字段错误个数
字符数
min=3 对 "你好" 不通过:字符串按字符数,而不是字节数
dive
切片元素的规则要写 dive 才生效(Items 没写,{} 不被检查)

用的是什么校验器

实测 · 本机执行输出
  binding/default_validator.go(Gin v1.12.0):
      88 | }
      89 | 
      90 | func (v *defaultValidator) lazyinit() {
      91 |     v.once.Do(func() {
      92 |         v.validate = validator.New()
      93 |         v.validate.SetTagName("binding")
      94 |     })

Gin 把 go-playground/validator/v10 包了一层,tag 名改成了 binding,第一次校验时才初始化。顶层是切片时 Gin 自己逐个元素调用校验器;其它非结构体类型直接放行。

FieldError 里有什么

实测 · 本机执行输出
  err=
        Req.Name             tag=min        param="3"          kind=string  value=ab
        Req.Age              tag=gte        param="18"         kind=int     value=17
        Req.Email            tag=email      param=""           kind=string  value=not-an-email
        Req.Role             tag=oneof      param="admin user guest" kind=string  value=root
        Req.Site             tag=url        param=""           kind=string  value=ftp//x
        Req.ID               tag=uuid       param=""           kind=string  value=123
        Req.Tags[1]          tag=alpha      param=""           kind=string  value=b1
        Req.Zip              tag=numeric    param=""           kind=string  value=12a45
        Req.Birth            tag=datetime   param="2006-01-02" kind=string  value=2024-13-01
        Req.IP               tag=ip         param=""           kind=string  value=999.1.1.1
        Req.Confirm          tag=eqfield    param="Pass"       kind=string  value=abc124
    --- 同一份结构体,全都合格:
  err= nil(校验通过)

每个失败的字段是一个 FieldError:Namespace()(带结构体名和下标的完整路径,如 Req.Tags[1])、Tag()、Param()(规则的参数,如 min=3 里的 3、oneof 里的取值列表)、Kind()、Value()。要把校验错误翻译成面向用户的文案,基本就靠 Tag 和 Param 查表。同一个字段多条规则时,第一条失败就停止,只报一项。

三种错误

实测 · 本机执行输出
    body={"name":"ab","age":17} →
      validator.ValidationErrors
      Key: 'Req.Name' Error:Field validation for 'Name' failed on the 'min' tag
      Key: 'Req.Age' Error:Field validation for 'Age' failed on the 'gte' tag
    body={"name":"alice","age":"x"} →
      *json.UnmarshalTypeError
      json: cannot unmarshal string into Go struct field Req.age of type int
    body={"name":"alice", →
      *errors.errorString
      unexpected EOF
    body=[] →
      *json.UnmarshalTypeError
      json: cannot unmarshal array into Go value of type main.Req

ShouldBindJSON 返回的错误不止校验失败一种:字段类型不对是 *json.UnmarshalTypeError(而且是在校验之前就报错,不会进入校验),JSON 语法错误是普通 error。用 errors.As 判断是不是 validator.ValidationErrors,再决定怎么回应客户端。

min 与 max 的含义

实测 · 本机执行输出
    string "abc"(3 字节 3 字符)   min=3: 通过
    string "你好"(6 字节 2 字符)    min=3: 不通过(按字符数,不是字节数)
    string "你好吗"(9 字节 3 字符)  min=3: 通过
    int 2   min=3: 不通过;  int 3: 通过;  int 100: 通过(数字比大小)
    float 2.9 min=3: 不通过;  float 3.0: 通过
    []int{1,2} min=3: 不通过;  []int{1,2,3}: 通过(比元素个数)
    map 2 个键 min=3: 不通过;  3 个键: 通过

同一条规则对不同类型含义不同:字符串数的是字符(rune)个数,不是字节数,所以中文 “你好” 只算 2;整数、浮点数比较数值;切片和 map 比较元素个数。len、eq 等也是同样的规则。

嵌套、切片、map

实测 · 本机执行输出
  err=
        Order.Addr.City      tag=required   param=""           kind=string  value=
        Order.Ship.City      tag=required   param=""           kind=string  value=
        Order.Items2[0].Sku  tag=required   param=""           kind=string  value=
        Order.ByKey[k].Sku   tag=required   param=""           kind=string  value=
    --- 顶层是切片:Gin 的 ValidateStruct 逐个校验元素
    binding.SliceValidationError [0]: Key: 'Item.Sku' Error:Field validation for 'Sku' failed on the 'required' tag | [1]: Key: 'Item.Sku' Error:Field validation for 'Sku' 

嵌套的结构体字段(含指针)会自动校验;切片和 map 里的元素必须在字段上加 dive,否则里面的 required 等规则不会被检查——这是最常见的“规则写了却没生效”。顶层切片则由 Gin 逐个校验,错误类型是 SliceValidationError,每项带 [下标] 前缀。

分步演示

校验规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew)/ Gin v1.12.0(go.mod 里 require,源码在模块缓存里);用 go run / go test 直接编译运行,需要库时经代理从 proxy.golang.org 下载;HTTP 实验用 httptest 或本机回环端口(9700 以上);计时用 time.Now / testing.B,数字每次运行会有小幅抖动。校验器是 Gin 内置的 validator v10.30.1(go-playground);请求用 httptest 直接调用 ServeHTTP;第四节直接用 validator.New() 并 SetTagName("binding"),规则含义与 Gin 内置的一致;每种情形各一次
    • 没有验证:validator 的所有内置规则(只试了约 15 个常见规则);错误信息的多语言翻译(universal-translator);跨字段规则 required_if、required_with 等;性能(每次校验的耗时与分配);并发调用时的缓存行为
    京ICP备2026067502号-1京公网安备11010502063487号