Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assert error type json.UnmarshalTypeError when caught by gin c.BindJSON

I'm trying to catch binding errors with gin gonic and it's working fine for all validation errors from go-playground/validator/v10 but i'm having an issue catching errors when unmarshalling into the proper data type.

Unsuccessful validation of a struct field will return a gin.ErrorTypeBind Type of error when using validator tags ( required, ...)

but if i have a struct

type Foo struct {
  ID int `json:"id"`
  Bar string `json:"bar"`
}

And the json i'm trying to pass is of a wrong format (passing a string instead of a number for id )

{
    "id":"string",
    "bar":"foofofofo"
}

It will fail with an error json: cannot unmarshal string into Go struct field Foo.id of type int

It is still caught as a gin.ErrorTypeBind in my handler as an error in binding but as i need to differentiate between validation error and unmarshalling error i'm having issues.

I have tried Type casting on validaton error doesn't work for unmarshalling : e.Err.(validator.ValidationErrors) will panic

or just errors.Is but this will not catch the error at all

    if errors.Is(e.Err, &json.UnmarshalTypeError{}) {
        log.Println("Json binding error")
    } 

My goal in doing so is to return properly formatted error message to the user. It's currently working well for all the validation logic but i can't seem to make it work for json data where incorrect data would be sent to me.

any ideas?

edit :

adding example to reproduce :

package main

import (
    "encoding/json"
    "errors"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

type Foo struct {
    ID  int    `json:"id" binding:"required"`
    Bar string `json:"bar"`
}

func FooEndpoint(c *gin.Context) {
    var fooJSON Foo
    err := c.BindJSON(&fooJSON)
    if err != nil {
        // caught and answer in the error MW
        return
    }
    c.JSON(200, "test")
}

func main() {
    api := gin.Default()
    api.Use(ErrorMW())
    api.POST("/foo", FooEndpoint)
    api.Run(":5000")
}

func ErrorMW() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        if len(c.Errors) > 0 {
            for _, e := range c.Errors {
                switch e.Type {
                case gin.ErrorTypeBind:
                    log.Println(e.Err)
                    var jsonErr json.UnmarshalTypeError
                    if errors.Is(e.Err, &jsonErr) {
                        log.Println("Json binding error")
                    }
                    // if errors.As(e.Err, &jsonErr) {
                    //  log.Println("Json binding error")
                    // }
                    // in reality i'm making it panic.
                    // errs := e.Err.(validator.ValidationErrors)
                    errs, ok := e.Err.(validator.ValidationErrors)
                    if ok {
                        log.Println("error trying to cast validation type")
                    }
                    log.Println(errs)
                    status := http.StatusBadRequest
                    if c.Writer.Status() != http.StatusOK {
                        status = c.Writer.Status()
                    }
                    c.JSON(status, gin.H{"error": "error"})
                default:
                    log.Println("other error")
                }

            }
            if !c.Writer.Written() {
                c.JSON(http.StatusInternalServerError, gin.H{"Error": "internal error"})
            }
        }
    }
}

trying sending a post request with a body

{
  "id":"rwerewr",
   "bar":"string"
}

interface conversion: error is *json.UnmarshalTypeError, not validator.ValidationErrors

this will work :

{
  "id":1,
   "bar":"string"
}

and this will (rightfully ) return Key: 'Foo.ID' Error:Field validation for 'ID' failed on the 'required' tag

{ "bar":"string" }

like image 363
michogph Avatar asked Apr 08 '26 04:04

michogph


1 Answers

errors.Is looks for an exact match (in your case, an empty instance of json.UnmarshalTypeError). Use errors.As instead:

var jsonErr *json.UnmarshalTypeError
if errors.As(e.Err, &jsonErr) {
    log.Println("Json binding error")
}

Playground example

However, as @LeGEC has pointed out, this assumes that gin's errors conform to the standard Unwrapper interface, which they don't.

like image 147
Flimzy Avatar answered Apr 10 '26 19:04

Flimzy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!