Model binding and validation

To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).

Gin uses go-playground/validator.v8 for validation. Check the full docs on tags usage here.

Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".

Also, Gin provides two sets of methods for binding:

  • Type - Must bind
    • Methods - Bind, BindJSON, BindQuery
    • Behavior - These methods use MustBindWith under the hood. If there is a binding error, the request is aborted with c.AbortWithError(400, err).SetType(ErrorTypeBind). This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. Note that if you try to set the response code after this, it will result in a warning [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. If you wish to have greater control over the behavior, consider using the ShouldBind equivalent method.
  • Type - Should bind
    • Methods - ShouldBind, ShouldBindJSON, ShouldBindQuery
    • Behavior - These methods use ShouldBindWith under the hood. If there is a binding error, the error is returned and it is the developer’s responsibility to handle the request and error appropriately.

When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use MustBindWith or ShouldBindWith.

You can also specify that specific fields are required. If a field is decorated with binding:"required" and has a empty value when binding, an error will be returned.

  1. // Binding from JSON
  2. type Login struct {
  3. User string `form:"user" json:"user" binding:"required"`
  4. Password string `form:"password" json:"password" binding:"required"`
  5. }
  6. func main() {
  7. router := gin.Default()
  8. // Example for binding JSON ({"user": "manu", "password": "123"})
  9. router.POST("/loginJSON", func(c *gin.Context) {
  10. var json Login
  11. if err := c.ShouldBindJSON(&json); err == nil {
  12. if json.User == "manu" && json.Password == "123" {
  13. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  14. } else {
  15. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  16. }
  17. } else {
  18. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  19. }
  20. })
  21. // Example for binding a HTML form (user=manu&password=123)
  22. router.POST("/loginForm", func(c *gin.Context) {
  23. var form Login
  24. // This will infer what binder to use depending on the content-type header.
  25. if err := c.ShouldBind(&form); err == nil {
  26. if form.User == "manu" && form.Password == "123" {
  27. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  28. } else {
  29. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  30. }
  31. } else {
  32. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  33. }
  34. })
  35. // Listen and serve on 0.0.0.0:8080
  36. router.Run(":8080")
  37. }

Sample request

  1. $ curl -v -X POST \
  2. http://localhost:8080/loginJSON \
  3. -H 'content-type: application/json' \
  4. -d '{ "user": "manu" }'
  5. > POST /loginJSON HTTP/1.1
  6. > Host: localhost:8080
  7. > User-Agent: curl/7.51.0
  8. > Accept: */*
  9. > content-type: application/json
  10. > Content-Length: 18
  11. >
  12. * upload completely sent off: 18 out of 18 bytes
  13. < HTTP/1.1 400 Bad Request
  14. < Content-Type: application/json; charset=utf-8
  15. < Date: Fri, 04 Aug 2017 03:51:31 GMT
  16. < Content-Length: 100
  17. <
  18. {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

Skip validate

When running the above example using the above the curl command, it returns error. Because the example use binding:"required" for Password. If use binding:"-" for Password, then it will not return error when running the above example again.