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.
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.
Most helpful comment
@alkesh26 you have to use
divetag that tells the validator to dive into a slice, array or map.Test:
Will output: