校验组件支持强大的递归校验(嵌套校验)特性。如果给定的校验数据中的属性或者键值为struct/map/slice类型时,将会被自动执行递归校验。我们来看几个示例:

示例1,递归校验:struct

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

type SearchReq struct {
	Key    string `v:"required"`
	Option SearchOption
}

type SearchOption struct {
	Page int `v:"min:1"`
	Size int `v:"max:100"`
}

func main() {
	var (
		ctx = gctx.New()
		req = SearchReq{
			Key: "GoFrame",
			Option: SearchOption{
				Page: 1,
				Size: 10000,
			},
		}
	)
	err := g.Validator().Data(req).Run(ctx)
	fmt.Println(err)
}

执行后,终端输出:

The Size value `10000` must be equal or lesser than 100

示例2,递归校验:slice

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	type Student struct {
		Name string `v:"required#Student Name is required"`
		Age  int   
	}
	type Teacher struct {
		Name     string  
		Students []Student 
	}
	var (
		ctx     = gctx.New()
		teacher = Teacher{}
		data    = g.Map{
			"name":     "john",
			"students": `[{"age":2},{"name":"jack", "age":4}]`,
		}
	)
	err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
	fmt.Println(err)
}

执行后,终端输出:

Student Name is required

示例3,递归校验:map

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	type Student struct {
		Name string `v:"required#Student Name is required"`
		Age  int
	}
	type Teacher struct {
		Name     string
		Students map[string]Student
	}
	var (
		ctx     = gctx.New()
		teacher = Teacher{
			Name: "Smith",
			Students: map[string]Student{
				"john": {Name: "", Age: 18},
			},
		}
	)
	err := g.Validator().Data(teacher).Run(ctx)
	fmt.Println(err)
}

执行后,终端输出:

Student Name is required

注意事项:空对象对递归校验的影响

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	type Student struct {
		Name string `v:"required"`
	}
	type Teacher struct {
		Students Student
	}
	var (
		ctx     = gctx.New()
		teacher = Teacher{}
		data    = g.Map{
			"students": nil,
		}
	)
	err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
	fmt.Println(err)
}

执行后,终端输出:

Student Name is required

有同学可能会觉得奇怪,明明我都没有传递Student字段值,为什么还会递归校验Student结构体里面的Name字段?这是因为这里的 Student 属性是个空结构体,是带有默认值的(Name默认值为空字符串)。递归校验里面,虽然 Student 不是必须参数,这个意思是你可以不传递,但是只要传递了就会按照里面属性的校验规则执行校验(带有默认值的空对象也算是有值)。可以对比和以下代码的差别:

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	type Student struct {
		Name string `v:"required"`
	}
	type Teacher struct {
		Students *Student
	}
	var (
		ctx     = gctx.New()
		teacher = Teacher{}
		data    = g.Map{
			"students": nil,
		}
	)
	err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
	fmt.Println(err)
}

和前一示例的唯一差异在于Student属性从结构体改为了结构体指针*Student,这样该属性不是空对象便没有默认值了。执行后,终端输出位空,表示校验通过。




Content Menu

  • No labels

5 Comments

  1. 这例子要写的这么复杂吗?直接一个二维数组不好吗?还搞个嵌套。一个批量将数据写数据库之前进行校验就好了,应该是大家用的最多的。

    1. 写嵌套的例子可以自行简化,而写简单的例子,则很难反过来。而且这里标题已经写着递归

  2. package main

    import (
    "fmt"

    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/os/gctx"
    )

    func main() {
    type Student struct {
    Name string `v:"required#Student Name is required"`
    Age  int   
    }
    type Teacher struct {
    Name     string  
    Students []*Student 
    }
    var (
    ctx     = gctx.New()
    teacher = Teacher{}
    data    = g.Map{
    "name":     "john",
    "students": `[{"age":2},{"name":"jack", "age":4}]`,
    }
    )
    err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
    fmt.Println(err)
    }


    当数据Slice递归的类型定义成 Students []*Student ,递归校验就失效了

  3. package main

    import (
        "fmt"

        "github.com/gogf/gf/v2/frame/g"
        "github.com/gogf/gf/v2/os/gctx"
    )

    type SearchReq struct {
        Key    string `v:"required"`
        Option *SearchOption
    }

    type SearchOption struct {
        Page int `v:"min:1"`
        Size int `v:"max:100"`
    }

    func main() {
        var (
            ctx = gctx.New()
            req = SearchReq{
                Key: "GoFrame",
                Option: &SearchOption{
                    Page: 1,
                    Size: 10000,
                },
            }
        )
        err := g.Validator().Data(req).Run(ctx)
        fmt.Println(err)
    }

    和楼上一样,当引用的struct定义为指针类型,就check失败了


  4. 數據校驗我是拿 
    github.com/gookit/validate
    這個套件來用,就是因為多層指針類型的關係
    我寫了一個 Helper Function 來處理

    func Validate(data any) (throw error) {
    value := reflect.ValueOf(data)

    for value.Kind() == reflect.Pointer {
    value = value.Elem()
    }

    switch value.Kind() {
    case reflect.Slice, reflect.Array:
    for index := 0; value.Len() > index; index++ {
    throw = Validate(value.Index(index).Interface())
    if nil != throw {
    return
    }
    }

    break

    case reflect.Map:
    iterator := value.MapRange()

    for iterator.Next() {
    throw = Validate(iterator.Key().Interface())
    if nil != throw {
    return
    }
    throw = Validate(iterator.Value().Interface())
    if nil != throw {
    return
    }
    }

    break

    case reflect.Struct:
    throw = validate.New(data).ValidateErr()
    break
    default:
    // g.Dump("default", value.Kind())
    }

    return
    }