I was trying to bind a nested struct from a json body using gin.
The issue I stumbled upon is that the required binding (seemingly) failed to be applied for the nested struct (Nested in the snippet below). However, it works fine for values of type string, int, etc, even if they are nested.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type Nested struct {
Hello string `json:"hello"`
}
type TransferCreateBody struct {
TestNested Nested `json:"nested" binding:"required"`
TestNotNested string `json:"not_nested" binding:"required"`
}
func main() {
g := gin.Default()
g.POST("/hello", func(c *gin.Context) {
var req TransferCreateBody
if err := c.ShouldBindJSON(&req); err != nil {
fmt.Println(err)
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"hello": req.TestNested.Hello})
})
g.Run(":9000")
}
curl -X POST http://localhost:9000/hello
-H "Content-Type: application/json"
-d '{"not_nested": "test"}'
How do I make the binding work with a nested struct ?
Ok, I got an explanation - this behavior is due to the fact that the "required" binding is actually checked after the json has been decoded into the target struct. That is, gin will check if the "required" parameter has its default (zero) value or is "properly" filled.
For a nested struct, the following would work:
because the zero value of
*Nestedisnil, not an empty instance ofNested. Another possibly confusing consequence is that the required binding will report an error if a required field is passed in the json body, but with its zero value (e.g.{"not_nested": ""}in the snippet above).This can also be avoided by using pointers everywhere in the structure to bind, by using a struct like
sql.NullXXXto bind the values, etc.