Description

I use ShouldBindJSON for validation and I have a model that has a field with the type float32 name 'salary' when I send string for field salary I expect to get a slice of errors of type go-playground/v10 validator.ValidationErrors or slice of json.UnmarshalTypeError but I get only *json.UnmarshalTypeError

How to reproduce

type User struct {
    Name   string  `json:"name" binding:"required"`
    Salary float32 `json:"salary" binding:"required"`
    Age uint8 `json:"age" binding:"required"`
}

func main() {
    r := gin.Default()
    r.POST("/users", func(c *gin.Context) {
        u := User{}
        if err := c.ShouldBindJSON(&u); err != nil {
            c.String(402, "type: %T", err)
            return
        }
        c.String(200, "good")
    })
    r.Run()
}

Expectations

it will be very nice if it return errors as validator.ValidationErrors

$ curl -X curl -X POST -H "Content-Type: application/json" -d '{"name":123,"salary":"5000","age":"22"}' http://localhost:8080/users
type: validator.ValidationErrors

or maybe return errors as slice of *json.UnmarshalTypeError so we can generate error messages for all in response

$ curl -X curl -X POST -H "Content-Type: application/json" -d '{"name":123,"salary":"5000","age":"22"}' http://localhost:8080/users
type:[]*json.UnmarshalTypeError

Actual result

it only returns an error for the first field so the user must send request 3 times to solve type problems of his entry

$ curl -X curl -X POST -H "Content-Type: application/json" -d '{"name":123,"salary":"5000","age":"22"}' http://localhost:8080/users
type: *json.UnmarshalTypeError

Environment

  • go version: 1.17.8
  • gin version (or commit ref): 1.7.7
  • operating system: ubuntu

Comment From: kszafran

Gin basically uses the encoding/json package to unmarshal the request body first, and then runs validation on it. In your case unmarshaling itself has failed. I don't think it's possible to convert json.UnmarshalTypeError to validator.ValidationErrors in all cases. The former error often has less information. (If I remember correctly, when unmarshaling time.Time fails, you're not even told what field caused the problem.)

Comment From: mahdimehrabi

so gin must use another package to unmarshal because I think it's a huge problem if we have this model fields

    Name   string  `json:"name" binding:"required"`
    Salary float32 `json:"salary" binding:"required"`
    Age uint8 `json:"age" binding:"required"`

and the user sends a request with this data

{
name:123,
salary:"5000",
age:"20"
}

we can only show one of this fields has a problem and the user must send 3 requests and solve its problems one by one to understand all of his fields types are wrong besides other problem

Can you contribute and fix this problem or suggest to me a solution to fix this in my project or suggest me an alternative for encoding/json package so I contribute and fix this problem

Comment From: kszafran

I'm not a maintainer of Gin, so it's up to you how you want to handle this. In my projects, I have a wrapper around Gin's ShouldBindJSON, which can handle both ValidationErrors and UnmarshalTypeError and I'm just accepting the limitation that I might not always be able to present the user with all the invalid fields at once.

There are many alternative JSON libraries out there, e.g. https://github.com/goccy/go-json and https://github.com/json-iterator/go. I think Gin even has support for jsoniter: https://github.com/gin-gonic/gin/blob/master/internal/json/jsoniter.go. I haven't tried using alternative JSON libs, though, so I don't know if they provide better error reporting or not. This article lists a few more JSON libs: https://www.pixelstech.net/article/1639285333-Popular-Golang-JSON-libraries-evaluation

Worst case scenario, you could try umarshaling twice. First to interface{}, where you can check that fields have the types you expect, and then a second time to the target type. It's not pretty, but it would be one way to approach this if it's critical to you.