Golang: Validate inner Struct field based on the values of one of its enclosing struct's field using required_if tag

huangapple go评论82阅读模式
英文:

Golang: Validate inner Struct field based on the values of one of its enclosing struct's field using required_if tag

问题

golang版本:1.18.3

验证器:github.com/go-playground/validator/v10

我想在加载到嵌套结构数据结构后验证传入的JSON有效负载。这是我的传入JSON有效负载:

{
    "irn": 1,
    "firstName": "Testing",
    "lastName": "Test",
    "cardType": "RECIPROCAL",
    "number": "2248974514",
    "cardExpiry": {
        "month": "01",
        "year": "2025"
    }
}

这是我的medicare.go文件:

package main

import (
    "encoding/json"

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

type Medicare struct {
    IRN           uint8
    FirstName     string
    MiddleInitial string
    LastName      string
    CardType      string `validate:"required,eq=RESIDENT|eq=RECIPROCAL|eq=INTERIM"`
    Number        string
    CardExpiry    *CardExpiry `validate:"required"`
}

type CardExpiry struct {
    Day   string
    Month string `validate:"required"`
    Year  string `validate:"required"`
}

这是我的测试函数:

func TestUserPayload(t *testing.T) {
    var m Medicare

    err := json.Unmarshal([]byte(jsonData), &m)
    if err != nil {
        panic(err)
    }

    validate := validator.New()
    err = validate.Struct(m)
    if err != nil {
        t.Errorf("error %v", err)
    }
}

我想使用validator/v10的"required_if"标签进行以下验证。这是验证逻辑:

if (m.CardType == "RECIPROCAL" || m.CardType == "INTERIM") &&
    m.CardExpiry.Day == "" {
    //validation error
}

"required_if"可以根据相同结构体中的字段值来使用,例如CardExpiry中的字段值:

Day   string `validate:"required_if=Month 01"`

我的问题是,

是否可以基于其封闭结构体(在本例中为Medicare结构体)的字段值进行验证?
例如:

Day   string `validate:"required_if=Medicare.CardType RECIPROCAL"`

如果可以,如何实现?

这是go playground上的代码示例:链接

英文:

golang version: 1.18.3

validator: github.com/go-playground/validator/v10

I want to validate an incoming JSON payload after loaded into nested struct data structure. Here's my incoming JSON payload,

 {
    	"irn": 1,
    	"firstName": "Testing",
    	"lastName": "Test",
    	"cardType": "RECIPROCAL",
    	"number": "2248974514",
    	"cardExpiry": {
    		"month": "01",
    		"year": "2025"
    	}
}

here's my medicare.go file

 package main

import (
	"encoding/json"

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

type Medicare struct {
	IRN           uint8
	FirstName     string
	MiddleInitial string
	LastName      string
	CardType      string `validate:"required,eq=RESIDENT|eq=RECIPROCAL|eq=INTERIM"`
	Number        string
	CardExpiry    *CardExpiry `validate:"required"`
}

type CardExpiry struct {
	Day   string
	Month string `validate:"required"`
	Year  string `validate:"required"`
}

Here's my test function

    func TestUserPayload(t *testing.T) {
	var m Medicare

	err := json.Unmarshal([]byte(jsonData), &m)
	if err != nil {
		panic(err)
	}

	validate := validator.New()
	err = validate.Struct(m)
	if err != nil {
		t.Errorf("error %v", err)
	}
}

I want to do the following validation using validator/v10's required_if tag.
Here's the validation logic,

if (m.CardType == "RECIPROCAL" || m.CardType == "INTERIM") &&
	m.CardExpiry.Day == "" {
	//validation error
}

required_if can be used based on the field values which are in the same struct (in this case CardExpiry)

Day   string `validate:"required_if=Month 01"`

My question is,

> can it be done based on the values of one of its enclosing struct's field (in this case Medicare struct)?
for ex:

Day   string `validate:"required_if=Medicare.CardType RECIPROCAL"`

> if can, how?

Here's the go playground code

答案1

得分: 2

你可以编写自定义验证,playground-solution,有关自定义验证的文档可以在这里找到:https://pkg.go.dev/github.com/go-playground/validator#CustomTypeFunc

英文:

you could write custom validation,
playground-solution, documentation for custom validation is available here https://pkg.go.dev/github.com/go-playground/validator#CustomTypeFunc.

答案2

得分: 2

稍微有点过时的问题,但是可以使用valix(https://github.com/marrow16/valix)来使用条件“开箱即用”地完成这样的事情。示例...

package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/marrow16/valix"
)

type Medicare struct {
	IRN           uint8  `json:"irn"`
	FirstName     string `json:"firstName"`
	MiddleInitial string `json:"middleInitial"`
	LastName      string `json:"lastName"`
	// set order on this property so that it is evaluated before 'CardExpiry' object is checked...
	// (and set a condition based on its value)
	CardType   string      `json:"cardType" v8n:"order:-1,required,&StringValidToken{['RESIDENT','RECIPROCAL','INTERIM']},&SetConditionFrom{Global:true}"`
	Number     string      `json:"number"`
	CardExpiry *CardExpiry `json:"cardExpiry" v8n:"required,notNull"`
}

type CardExpiry struct {
	// this property is required when a condition of `RECIPROCAL` has been set (and unwanted when that condition has not been set)...
	Day   string `json:"day" v8n:"required:RECIPROCAL,unwanted:!RECIPROCAL"`
	Month string `json:"month" v8n:"required"`
	Year  string `json:"year" v8n:"required"`
}

var medicareValidator = valix.MustCompileValidatorFor(Medicare{}, nil)

func main() {
	jsonData := `{
		"irn": 1,
		"firstName": "Testing",
		"lastName": "Test",
		"cardType": "RECIPROCAL",
		"number": "2248974514",
		"cardExpiry": {
			"month": "01",
			"year": "2025"
		}
	}`

	medicare := &Medicare{}
	ok, violations, _ := medicareValidator.ValidateStringInto(jsonData, medicare)
	// should fail with 1 violation...
	fmt.Printf("First ok?: %v\n", ok)
	for i, v := range violations {
		fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
	}

	jsonData = `{
			"irn": 1,
			"firstName": "Testing",
			"lastName": "Test",
			"cardType": "RECIPROCAL",
			"number": "2248974514",
			"cardExpiry": {
				"day": "01",
				"month": "01",
				"year": "2025"
			}
		}`
	ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
	// should be ok...
	fmt.Printf("Second ok?: %v\n", ok)

	jsonData = `{
			"irn": 1,
			"firstName": "Testing",
			"lastName": "Test",
			"cardType": "INTERIM",
			"number": "2248974514",
			"cardExpiry": {
				"month": "01",
				"year": "2025"
			}
		}`
	ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
	// should be ok...
	fmt.Printf("Third ok?: %v\n", ok)

	jsonData = `{
			"irn": 1,
			"firstName": "Testing",
			"lastName": "Test",
			"cardType": "INTERIM",
			"number": "2248974514",
			"cardExpiry": {
				"day": "01",
				"month": "01",
				"year": "2025"
			}
		}`
	ok, violations, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
	fmt.Printf("Fourth ok?: %v\n", ok)
	for i, v := range violations {
		fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
	}

	// or validate directly from a http request...
	req, _ := http.NewRequest("POST", "", strings.NewReader(jsonData))
	ok, violations, _ = medicareValidator.RequestValidateInto(req, medicare)
	fmt.Printf("Fourth (as http.Request) ok?: %v\n", ok)
	for i, v := range violations {
		fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
	}
}

go-playground

披露:我是Valix的作者

英文:

Slightly old question, but valix (https://github.com/marrow16/valix) can do such things 'out-of-the-box' using conditions. Example...

package main
import (
"fmt"
"net/http"
"strings"
"github.com/marrow16/valix"
)
type Medicare struct {
IRN           uint8  `json:"irn"`
FirstName     string `json:"firstName"`
MiddleInitial string `json:"middleInitial"`
LastName      string `json:"lastName"`
// set order on this property so that it is evaluated before 'CardExpiry' object is checked...
// (and set a condition based on its value)
CardType   string      `json:"cardType" v8n:"order:-1,required,&StringValidToken{['RESIDENT','RECIPROCAL','INTERIM']},&SetConditionFrom{Global:true}"`
Number     string      `json:"number"`
CardExpiry *CardExpiry `json:"cardExpiry" v8n:"required,notNull"`
}
type CardExpiry struct {
// this property is required when a condition of `RECIPROCAL` has been set (and unwanted when that condition has not been set)...
Day   string `json:"day" v8n:"required:RECIPROCAL,unwanted:!RECIPROCAL"`
Month string `json:"month" v8n:"required"`
Year  string `json:"year" v8n:"required"`
}
var medicareValidator = valix.MustCompileValidatorFor(Medicare{}, nil)
func main() {
jsonData := `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "RECIPROCAL",
"number": "2248974514",
"cardExpiry": {
"month": "01",
"year": "2025"
}
}`
medicare := &Medicare{}
ok, violations, _ := medicareValidator.ValidateStringInto(jsonData, medicare)
// should fail with 1 violation...
fmt.Printf("First ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "RECIPROCAL",
"number": "2248974514",
"cardExpiry": {
"day": "01",
"month": "01",
"year": "2025"
}
}`
ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
// should be ok...
fmt.Printf("Second ok?: %v\n", ok)
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "INTERIM",
"number": "2248974514",
"cardExpiry": {
"month": "01",
"year": "2025"
}
}`
ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
// should be ok...
fmt.Printf("Third ok?: %v\n", ok)
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "INTERIM",
"number": "2248974514",
"cardExpiry": {
"day": "01",
"month": "01",
"year": "2025"
}
}`
ok, violations, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
fmt.Printf("Fourth ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
// or validate directly from a http request...
req, _ := http.NewRequest("POST", "", strings.NewReader(jsonData))
ok, violations, _ = medicareValidator.RequestValidateInto(req, medicare)
fmt.Printf("Fourth (as http.Request) ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
}

On go-playground

Disclosure: I am the author of Valix

huangapple
  • 本文由 发表于 2022年7月21日 09:51:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/73059828.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定