如果Gin验证失败,如何返回自定义错误

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).