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"
}
Activity
zemzale commentedon Apr 15, 2025
I tried to look into some ways, but I don't think there are some easy ways how to do it.
The embed is just now a part of the namespace, just like
Adult
so unless we add some new way how to control this, I don't think it's possible with embeds.deankarn commentedon Apr 15, 2025
@zemzale is correct, there is no current way to control this.
This was a necessary design decision around correctness that needed to be made as the struct is only pseudo embedded in Go.
The reason why is because if any field with the same name exists on both the outer and “embedded” struct, there is/would be no way to differentiate between them. Both fields can exist this way and why I say it’s only pseudo embedded and in reality is more made like a field with the structs name on the outer struct and Gos json marshal & unmarshal treat it as if it’s truly embedded.
In your example this is very unlikely to happen/have the same named field, however I have experienced this situation myself and seen it in others examples as well.
I would provide an example but answering from my phone atm.
A complete aside fwiw, I have only seen embedding like this cause problems longer term as projects grow as because of the aforementioned field “collision” possibilities and someone can break many things by adding a field to one of the “embedded” structs in isolation not knowing/realizing it’s affects on the outer structs that may embed it. I personally wouldn’t recommend it if avoidable.
arxeiss commentedon Apr 15, 2025
Thank you for your feedback. I totally understand you want to keep that behavior, and your concerns with embedding can lead to hell in the future. However what I'm building is HTTP proxy between our internal Proto definitions and public API. And I embed the common parts, that is in Proto too. What I presented above was to demonstrate that. Not a real use case. So I believe we can continue with that without causing collisions.
But back to feature request. As I said, I understand you want to keep current behavior and that is fine. So I was thinking if it could be controlled by struct tag, that would tell "skip that embedding".
Because embedding can have struct tag too. And actually that affect json Marshaller as well. So it would have to be properly tested.
See the example below with JSON struct tag at embedding.
I could try to prepare PR, but I don't want to spend time, if there wouldn't be agreement and PR would be closed later.
zemzale commentedon Apr 30, 2025
I wouldn't be opposed to have this controlled by a struct tag. I am not fully sure about the naming and such, but that can be figured out a bit later too.
arxeiss commentedon Jun 5, 2025
Could you please check PR #1426 which is handling this issue. So at least we can move further with the discussion?
Thank you 🙏