当 gin c.BindJSON 捕获时如何断言错误类型 json.UnmarshalTypeError

mic*_*gph 3 validation error-handling json go go-gin

我正在尝试使用 gin gonic 捕获绑定错误,并且它可以很好地处理来自 go-playground/validator/v10 的所有验证错误,但在解组到正确的数据类型时遇到捕获错误的问题。

使用验证器标签时,结构体字段验证失败将返回gin.ErrorTypeBind错误类型(必需,...)

但如果我有一个结构

type Foo struct {
  ID int `json:"id"`
  Bar string `json:"bar"`
}
Run Code Online (Sandbox Code Playgroud)

我试图传递的 json 格式错误(传递字符串而不是 id 的数字)

{
    "id":"string",
    "bar":"foofofofo"
}
Run Code Online (Sandbox Code Playgroud)

它将因错误而失败json: cannot unmarshal string into Go struct field Foo.id of type int

它仍然在我的处理程序中被捕获为gin.ErrorTypeBind绑定错误,但由于我需要区分验证错误和解组错误,所以我遇到了问题。

我尝试过验证错误上的类型转换不适用于解组: e.Err.(validator.ValidationErrors)会恐慌

或者只是错误。但这根本不会捕获错误

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

我这样做的目标是向用户返回格式正确的错误消息。它目前对于所有验证逻辑都运行良好,但我似乎无法使其适用于 json 数据,其中错误的数据会发送给我。

有任何想法吗?

编辑 :

添加示例来重现:

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"})
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

尝试发送带有正文的 post 请求

{
  "id":"rwerewr",
   "bar":"string"
}
Run Code Online (Sandbox Code Playgroud)

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

这会起作用:

{
  "id":1,
   "bar":"string"
}
Run Code Online (Sandbox Code Playgroud)

这将(正确地)返回 Key: 'Foo.ID' Error:Field validation for 'ID' failed on the 'required' tag

{“栏”:“字符串”}

Fli*_*mzy 7

errors.Is查找完全匹配(在您的情况下,是 的空实例json.UnmarshalTypeError)。改用errors.As

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

游乐场示例

然而,正如 @LeGEC 所指出的,这假设 gin 的错误符合标准Unwrapper接口,但事实并非如此。