For example I've got the following struct
type Address struct {
City string `json:"city" binding:"required"`
AddressLine string `json:"address_line" binding:"required"`
}
and I've got the following function to handle request from users
func AddressCreate(c *gin.Context) {
var address Address
if err := c.BindJSON(&address); err == nil {
// if everything is good save to database
// and return success message
db.Create(&address)
c.JSON(http.StatusOK, gin.H {"status":"success"})
} else {
c.JSON(http.StatusBadRequest, err)
}
}
Expected behavior is to return json formatted this way
[
{
"city":"required"
}
{
"address_line":"required"
}
]
But I'm getting error formatted like this
"Address.City": {
"FieldNamespace": "Address.City",
"NameNamespace": "City",
"Field": "City",
"Name": "City",
"Tag": "required",
"ActualTag": "required",
"Kind": 24,
"Type": {},
"Param": "",
"Value": ""
},
"Address.AddressLine": {
"FieldNamespace": "AddressLine",
"NameNamespace": "AddressLine",
"Field": "AddressLine",
"Name": "AddressLine",
"Tag": "required",
"ActualTag": "required",
"Kind": 24,
"Type": {},
"Param": "",
"Value": ""
}
What did I tried:
I created function which casts error
to ValidationErrors
and iterates through all FieldError
's in it
func ListOfErrors(e error) []map[string]string {
ve := e.(validator.ValidationErrors)
InvalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
// field := reflect.TypeOf(e.NameNamespace)
errors[e.Name] = e.Tag
InvalidFields = append(InvalidFields, errors)
}
return InvalidFields
}
The output look's much better
[
{
"City":"required"
},
{
"AddressLine":"required"
}
]
But I cannot solve the problem with the name of the fields. I cannot swap Name
into name
which I noted in structs tag json:"city"
. So my question is did I choose correct way to solve the problem if the answer is yes how to get structs json tag for field?
You can use ToSnake
to snake case the names:
import (
"unicode"
)
// ToSnake convert the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
func ToSnake(in string) string {
runes := []rune(in)
length := len(runes)
var out []rune
for i := 0; i < length; i++ {
if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
out = append(out, '_')
}
out = append(out, unicode.ToLower(runes[i]))
}
return string(out)
}
func ListOfErrors(e error) []map[string]string {
ve := e.(validator.ValidationErrors)
invalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
errors[ToSnake(e.Name)] = e.Tag
invalidFields = append(InvalidFields, errors)
}
return invalidFields
}
If you want it to be same as defined in your json tag, then you should use reflection to pull that tag from your data type.
I don't have your libraries, so can't compile and check it. But I believe what you are after should go along those lines:
func ListOfErrors(address *Address, e error) []map[string]string {
ve := e.(validator.ValidationErrors)
InvalidFields := make([]map[string]string, 0)
for _, e := range ve {
errors := map[string]string{}
// field := reflect.TypeOf(e.NameNamespace)
field, _ := reflect.TypeOf(address).Elem().FieldByName(e.Name)
jsonTag := string(field.Tag.Get("json"))
errors[jsonTag] = e.Tag
InvalidFields = append(InvalidFields, errors)
}
return InvalidFields
}
Note that it is a bit contrived as type of address
parameter is essentially known. So, not strictly required as a function parameter. But you can change address *Address
to address interface{}
and use it for other types too.
Disclaimer: I skipped error checking for brevity, but you certainly should check for errors in your code (e.g. no such field error or no json tag on that field).