Gin: Required Issue in binding

Created on 26 Jul 2017  路  9Comments  路  Source: gin-gonic/gin

I have an usecase like this.

type user struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

type data struct {
    User []user `json:"user" binding:"required"`
}

//GetDataTest will get test data
func GetDataTest(c *gin.Context) {
    var data data
    err := c.Bind(&data)
    if err == nil {
        fmt.Printf("%+v", data)
        c.JSON(http.StatusOK, gin.H{
            "message": "done",
        })
    } else {
        c.JSON(http.StatusBadRequest, gin.H{
            "message": err.Error(),
        })
    }
}

By post body is

{
    "user": [{
        "email": "[email protected]",
        "name": "alu"
    },
    {
        "email": "",
        "name": "alu"   
    }]
}

In the above case email field validation of required is not working. It is because of list of struct or am missing something.

Most helpful comment

@alkesh26 you have to use dive tag that tells the validator to dive into a slice, array or map.

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type user struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

type data struct {
    User []user `json:"user" binding:"required,dive"` // use dive tag
}

//GetDataTest will get test data
func GetDataTest(c *gin.Context) {
    var data data
    err := c.Bind(&data)
    if err == nil {
        fmt.Printf("%+v", data)
        c.JSON(http.StatusOK, gin.H{
            "message": "done",
        })
    } else {
        c.JSON(http.StatusBadRequest, gin.H{
            "message": err.Error(),
        })
    }
}



func main(){

    route := gin.Default()
    route.POST("/", GetDataTest)
    route.Run(":8080")
}

Test:

$ curl -H "Content-Type: application/json" -X POST --data '{"user": [{"email": "[email protected]","name": "alu"},{"email": "","name": "alu"}]}' http://localhost:8080/

Will output:

{"message":"Key: 'data.User[1].Email' Error:Field validation for 'Email' failed on the 'required' tag"}

All 9 comments

Maybe you should see #815

@thinkerou Our issue is that when we have list of nested struct in request payload, binding:"required" of internal struct does not work as mentioned in above example.

type user struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

type data struct {
    User []user `json:"user" binding:"required"`
}

When my use this below json in request body

{
    "user": [{
        "email": "[email protected]",
        "name": "alu"
    },
    {
        "email": "",
        "name": "alu"   
    }]
}

It passes without giving bind error. Since email is required field this should have failed

@alkesh26 you have to use dive tag that tells the validator to dive into a slice, array or map.

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type user struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

type data struct {
    User []user `json:"user" binding:"required,dive"` // use dive tag
}

//GetDataTest will get test data
func GetDataTest(c *gin.Context) {
    var data data
    err := c.Bind(&data)
    if err == nil {
        fmt.Printf("%+v", data)
        c.JSON(http.StatusOK, gin.H{
            "message": "done",
        })
    } else {
        c.JSON(http.StatusBadRequest, gin.H{
            "message": err.Error(),
        })
    }
}



func main(){

    route := gin.Default()
    route.POST("/", GetDataTest)
    route.Run(":8080")
}

Test:

$ curl -H "Content-Type: application/json" -X POST --data '{"user": [{"email": "[email protected]","name": "alu"},{"email": "","name": "alu"}]}' http://localhost:8080/

Will output:

{"message":"Key: 'data.User[1].Email' Error:Field validation for 'Email' failed on the 'required' tag"}

@easonlin404 It worked. Thanks a lot. Closing this issue.


Edit: Actually related and similar to #815

@easonlin404 I am not sure if I should create a separate issue for this, but did you notice that the response does not have Content-type application/json header?

位 curl -v -X POST http://localhost:8080/ -H 'content-type: application/json' -d '{"user": [{"email": "[email protected]","name": "alu"},{"email": "","name": "alu"}]}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* STATE: INIT => CONNECT handle 0x20082628; line 1407 (connection #-5000)
* Added connection 0. The cache now contains 1 members
*   Trying ::1...
* TCP_NODELAY set
* STATE: CONNECT => WAITCONNECT handle 0x20082628; line 1460 (connection #0)
* Connected to localhost (::1) port 8080 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x20082628; line 1567 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => DO handle 0x20082628; line 1585 (connection #0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 78
>
* upload completely sent off: 78 out of 78 bytes
* STATE: DO => DO_DONE handle 0x20082628; line 1664 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x20082628; line 1791 (connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x20082628; line 1801 (connection #0)
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 400 Bad Request
< Date: Mon, 31 Jul 2017 05:44:06 GMT
< Content-Length: 103
< Content-Type: text/plain; charset=utf-8
<
* STATE: PERFORM => DONE handle 0x20082628; line 1965 (connection #0)
* multi_done
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
* Expire cleared
{"message":"Key: 'data.User[1].Email' Error:Field validation for 'Email' failed on the 'required' tag"

This happens with all variants of Bind because they use MustBindWith:

// MustBindWith binds the passed struct pointer using the specified binding
// engine. It will abort the request with HTTP 400 if any error ocurrs.
// See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
    if err = c.ShouldBindWith(obj, b); err != nil {
        c.AbortWithError(400, err).SetType(ErrorTypeBind)
    }

    return
}

@sudo-suhas yes, if you want to get Content-Type application/json header when validating failed , you can use ShouldBindWith instead of Bind func that will not call AbortWithError:

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding" //add binding import
)

//GetDataTest will get test data
func GetDataTest(c *gin.Context) {
    var data data

    err := c.ShouldBindWith(&data, binding.JSON) //instead of  c.Bind(&data)
    if err == nil {
        fmt.Printf("%+v", data)
        c.JSON(http.StatusOK, gin.H{
            "message": "done",
        })
    } else {
        c.JSON(407, gin.H{
            "message": err.Error(),
        })
    }
}

Maybe we can add Bind ,MustBindWith and ShouldBindWith more doc at RAEDME.

@easonlin404 But what if I want its prompts to be customized in Chinese?

@easonlin404 Your solution just made my day. Thanks a lot!

@easonlin404 it helps me, thx.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Bloomca picture Bloomca  路  3Comments

cxk280 picture cxk280  路  3Comments

ccaza picture ccaza  路  3Comments

frederikhors picture frederikhors  路  3Comments

olegsobchuk picture olegsobchuk  路  3Comments