Skip to content

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

@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"
}

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

    Issue actions