-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
We are using Validator to validate incoming JSON requests. And because all requests contains common attributes we are using embedding (Human
in the example below).
Since that embedding has no tags (in Adult), it works exactly as we want. When json.Unmarshal
(or json.Marshal) is called it works as we want. In example below, first_name
and last_name
are in JSON as top-level attributes.
But the issue happen during validation. Because the error contains those errors:
"Adult.id_card_number": "Failed on tag: required"
"Adult.Human.first_name": "Failed on tag: min",
"Adult.address.city": "Failed on tag: required",
The fact, that "Adult"
is there we don't care much. It is easy just to remove everything before first .
.
After we do trimming, we will have id_card_number
and address.city
keys which are correct. They respect same structure as input JSON. But Human.first_name
is the issue. Because in input JSON there is no "Human"
key at all.
My question is, if there is a way not to include name of embedded structure into namespace?
package main
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/go-playground/validator/v10"
)
type Human struct {
FirstName string `json:"first_name" validate:"required,min=2"`
LastName string `json:"last_name" validate:"required,min=2"`
}
type Address struct {
Street string `json:"street" validate:"required,min=2"`
City string `json:"city" validate:"required,min=2"`
}
// User contains user information
type Adult struct {
Human
Address Address `json:"address"`
IdCardNumber string `json:"id_card_number" validate:"required,min=10"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
validate = validator.New(func(v *validator.Validate) {
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
// skip if tag key says it should be ignored
if name == "-" {
return ""
}
return name
})
})
data := Adult{}
err := json.Unmarshal([]byte(`{
"first_name":"I",
"last_name":"O",
"address":{
"street":"X"
}
}`), &data)
if err != nil {
panic(err)
}
err = validate.Struct(data)
jsonFieldErrResponse := map[string]string{}
jsonNsErrResponse := map[string]string{}
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
jsonFieldErrResponse[err.Field()] = "Failed on tag: " + err.ActualTag()
jsonNsErrResponse[err.Namespace()] = "Failed on tag: " + err.ActualTag()
}
}
d, _ := json.Marshal(jsonFieldErrResponse)
var df bytes.Buffer
json.Indent(&df, d, "", " ")
fmt.Println("Field err:", df.String())
d, _ = json.Marshal(jsonNsErrResponse)
df.Reset()
json.Indent(&df, d, "", " ")
fmt.Printf("Namespace err: %s", df.String())
}
Output from above the code is
Field err: {
"city": "Failed on tag: required",
"first_name": "Failed on tag: min",
"id_card_number": "Failed on tag: required",
"last_name": "Failed on tag: min",
"street": "Failed on tag: min"
}
Namespace err: {
"Adult.Human.first_name": "Failed on tag: min",
"Adult.Human.last_name": "Failed on tag: min",
"Adult.address.city": "Failed on tag: required",
"Adult.address.street": "Failed on tag: min",
"Adult.id_card_number": "Failed on tag: required"
}