Description

I'm trying to bind JSON and a URI in one request but havn't found out how to yet.

Here's my struct:

type ResetPassword struct {
    Code     string `uri:"code" binding:"required"`
    Password string `json:"password" binding:"required,min=6,max=20"`
}

I'm using c.Bind():

func verifyPasswordReset(c *gin.Context) {
    var data ResetPassword
    if err := c.Bind(&data); err != nil {
        c.JSON(400, gin.H{"error": err})
        return
    }
    fmt.Printf("%+v\n", data)
    c.JSON(http.StatusOK, data)
}

When I make this request it says the code is required:

curl -H "content-type:application/json" -d '{"password":"hunter15"}' "localhost:8080/user/password_reset/jksakhdkh"                                                                     Sun Mar 24 14:29:27 2019
{"error":{".Code":{"FieldNamespace":".Code","NameNamespace":"Code","Field":"Code","Name":"Code","Tag":"required","ActualTag":"required","Kind":24,"Type":{},"Param":"","Value":""}}}

I found a related issue #811 but it wasn't actually solved before being closed.

Comment From: thinkerou

you should split bind uri and json to two struct.

Comment From: montanaflynn

@thinkerou ok, I split it into two structs and used the respective binds:

    if err := c.ShouldBindUri(&dataURI); err != nil {
        c.JSON(400, gin.H{"error": err})
        return
    }

    if err := c.ShouldBindJSON(&dataJSON); err != nil {
        c.JSON(400, gin.H{"error": err})
        return
    }

That works fine and removes my workaround of using c.Params.ByName("code"), thanks!

Comment From: m430

@thinkerou Why not use the uri and form tag together? I think it would be convenient.

Comment From: invisibleDesigner

no, I want to use it in one struct

Comment From: chengr4

At 2023, we still should split it into two structs?

Comment From: TaylorDurden

At 2023, we still should split it into two structs? Here is a example:

type updateAccountRequest struct {
    ID      int64 `uri:"id" binding:"required,min=1"`
    Balance int64 `json:"balance"`
}

func (server *Server) updateAccountHandler(ctx *gin.Context) {
    var req updateAccountRequest
    if err := ctx.ShouldBindUri(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }

    if err := ctx.ShouldBindJSON(&req); err != nil {
        ctx.JSON(http.StatusBadRequest, errorResponse(err))
        return
    }
    arg := db.UpdateAccountParams{
        ID:      req.ID,
        Balance: req.Balance,
    }
    updatedAccount, err := server.store.UpdateAccount(ctx, arg)
    if err != nil {
        if err == sql.ErrNoRows {
            ctx.JSON(http.StatusNotFound, errorResponse(err))
            return
        }
        ctx.JSON(http.StatusInternalServerError, errorResponse(err))
        return
    }

    ctx.JSON(http.StatusOK, updatedAccount)
}

Comment From: QingShan-Xu

Balance int64 json:"balance"

If you set Balance int64 json:"balance" binding:"required", it will still fail because shoudBindUri binds json:"balance" but there is no balance in the uri.