Skip to content

Is there a way not to include name of Embedded structure in Namespace for error? #1413

Open
@arxeiss

Description

@arxeiss

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

zemzale commented on Apr 15, 2025

@zemzale
Member

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

deankarn commented on Apr 15, 2025

@deankarn
Contributor

@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

arxeiss commented on Apr 15, 2025

@arxeiss
Author

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.

type Human struct {
	FirstName string `json:"first_name" validate:"required,min=2"`
	LastName  string `json:"last_name" validate:"required,min=2"`
}

// User contains user information
type Adult struct {
	Human `json:"human"` // <- ---- SEE THIS ---
	IdCardNumber string `json:"id_card_number" validate:"required,min=10"`
}
// Without `json:"human"` at embedding 
{"first_name":"I","last_name":"O","id_card_number":"123456789"}

// With `json:"human"` at embedding 
{"human":{"first_name":"I","last_name":"O"},"id_card_number":"123456789"}
zemzale

zemzale commented on Apr 30, 2025

@zemzale
Member

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

arxeiss commented on Jun 5, 2025

@arxeiss
Author

Could you please check PR #1426 which is handling this issue. So at least we can move further with the discussion?
Thank you 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @arxeiss@deankarn@zemzale

        Issue actions

          Is there a way not to include name of Embedded structure in Namespace for error? · Issue #1413 · go-playground/validator