英文:
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)
}
}
披露:我是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)
}
}
Disclosure: I am the author of Valix
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论