The validation component supports powerful recursive validation (nested validation) features. If the properties or key values in the given validation data are of type struct/map/slice, recursive validation will be automatically performed. Let’s look at a few examples:

Example 1, Recursive Validation: struct

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. type SearchReq struct {
  8. Key string `v:"required"`
  9. Option SearchOption
  10. }
  11. type SearchOption struct {
  12. Page int `v:"min:1"`
  13. Size int `v:"max:100"`
  14. }
  15. func main() {
  16. var (
  17. ctx = gctx.New()
  18. req = SearchReq{
  19. Key: "GoFrame",
  20. Option: SearchOption{
  21. Page: 1,
  22. Size: 10000,
  23. },
  24. }
  25. )
  26. err := g.Validator().Data(req).Run(ctx)
  27. fmt.Println(err)
  28. }

After execution, the terminal outputs:

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

Example 2, Recursive Validation: slice

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. func main() {
  8. type Student struct {
  9. Name string `v:"required#Student Name is required"`
  10. Age int
  11. }
  12. type Teacher struct {
  13. Name string
  14. Students []Student
  15. }
  16. var (
  17. ctx = gctx.New()
  18. teacher = Teacher{}
  19. data = g.Map{
  20. "name": "john",
  21. "students": `[{"age":2},{"name":"jack", "age":4}]`,
  22. }
  23. )
  24. err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
  25. fmt.Println(err)
  26. }

After execution, the terminal outputs:

  1. Student Name is required

Example 3, Recursive Validation: map

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. func main() {
  8. type Student struct {
  9. Name string `v:"required#Student Name is required"`
  10. Age int
  11. }
  12. type Teacher struct {
  13. Name string
  14. Students map[string]Student
  15. }
  16. var (
  17. ctx = gctx.New()
  18. teacher = Teacher{
  19. Name: "Smith",
  20. Students: map[string]Student{
  21. "john": {Name: "", Age: 18},
  22. },
  23. }
  24. )
  25. err := g.Validator().Data(teacher).Run(ctx)
  26. fmt.Println(err)
  27. }

After execution, the terminal outputs:

  1. Student Name is required

Note: Impact of Empty Objects on Recursive Validation

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. func main() {
  8. type Student struct {
  9. Name string `v:"required"`
  10. }
  11. type Teacher struct {
  12. Students Student
  13. }
  14. var (
  15. ctx = gctx.New()
  16. teacher = Teacher{}
  17. data = g.Map{
  18. "students": nil,
  19. }
  20. )
  21. err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
  22. fmt.Println(err)
  23. }

After execution, the terminal outputs:

  1. Student Name is required

Some may find it strange why the Name field of the Student struct is validated even though the Student field value was not passed. This is because the Student property is an empty struct with default values (the Name default value is an empty string). In recursive validation, although Student is not a required parameter, meaning you could choose not to pass it, if it is passed, it will be validated according to the validation rules of the properties within it (an empty object with default values is also considered to have a value). You can compare this with the differences in the code below:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. func main() {
  8. type Student struct {
  9. Name string `v:"required"`
  10. }
  11. type Teacher struct {
  12. Students *Student
  13. }
  14. var (
  15. ctx = gctx.New()
  16. teacher = Teacher{}
  17. data = g.Map{
  18. "students": nil,
  19. }
  20. )
  21. err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
  22. fmt.Println(err)
  23. }

The only difference from the previous example is that the Student property has been changed from a struct to a struct pointer *Student, thus the property is no longer an empty object with default values. After execution, the terminal outputs nothing, indicating validation passed.