Gin: Question: How bind JSON array

Created on 11 Oct 2016  Â·  6Comments  Â·  Source: gin-gonic/gin

I have JSON in the request body which I would like to bind into an array. I tried several ways, but nothing seems to work. Can someone please provide an example where a JSON body contains an array of structs which bound? I know there are people who use maps. I prefer to stick with struct objects. Any tips would be greatly appreciated.

Thanks :)

question

Most helpful comment

you mean this? It seems possible.

package main

import (
    "net/http"

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

type CreateParams struct {
    Username     string `json:"username"`
    Guests       Guests `json:"guests"`
    RoomType     string `json:"roomType"`
    CheckinDate  string `json:"checkinDate"`
    CheckoutDate string `json:"checkoutDate"`
}

type Guests struct {
    Person []Person `json:"person"`
}

type Person struct {
    Firstname string `json:"firstname"`
    Lastname  string `json:"lastname"`
}

func main() {
    r := gin.New()
    r.POST("/", func(c *gin.Context) {
        var f CreateParams
        if err := c.BindJSON(&f); err != nil {
            return
        }
        c.IndentedJSON(http.StatusOK, f)
    })
    r.Run(":4000")
}

run the server

go run /tmp/test.go

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /                         --> main.main.func1 (1 handlers)
[GIN-debug] Listening and serving HTTP on :4000

access from curl

curl 0:4000 -X POST -d '{"username":"foo","guests":{"person":[{"firstname":"foobar","lastname":"barfoo"},{"firstname":"foofoo","lastname":"barbar"}]}}'

{
    "username": "foo",
    "guests": {
        "person": [
            {
                "firstname": "foobar",
                "lastname": "barfoo"
            },
            {
                "firstname": "foofoo",
                "lastname": "barbar"
            }
        ]
    },
    "roomType": "",
    "checkinDate": "",
    "checkoutDate": ""
}

All 6 comments

@RAndrews137 raw arrays/slices/lists are not supported for binding, here is an example to bind arrays inside a JSON object or through form url encoded values:

package main

import (
    "fmt"

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

type List struct {
    Messages []string `binding:"required"`
}

func main() {
    router := gin.Default()

    // curl -X POST -v -d "{\"Messages\": [\"this\", \"that\"]}" localhost:8080/postJSON
    router.POST("/postJSON", func(c *gin.Context) {
        data := new(List)
        err := c.BindJSON(data)
        if err != nil {
            c.AbortWithError(400, err)
            return
        }
        c.String(200, fmt.Sprintf("%#v", data))
    })

    //curl -X POST -v -d "Messages=this&Messages=that" localhost:8080/postFORM
    router.POST("/postFORM", func(c *gin.Context) {
        data := new(List)
        err := c.Bind(data)
        if err != nil {
            c.AbortWithError(400, err)
            return
        }
        c.String(200, fmt.Sprintf("%#v", data))
    })

    router.Run(":8080")
}

Thanks. For now, I am just using JSON decoder and go-validator separately rather than the Gin binding. It can handle an array of structs.

Is it possible to bind an array where the array is located in a nested struct ? for example, my struct looks something like this:

package params

type CreateParams struct {
    Username     string `json:"username"`
    Guests       Guests `json:"guests"`
    RoomType     string `json:"roomType"`
    CheckinDate  string `json:"checkinDate"`
    CheckoutDate string `json:"checkoutDate"`
}

type Guests struct {
    Person []Person
}

type Person struct {
    firstname string
    lastname  string
}

you mean this? It seems possible.

package main

import (
    "net/http"

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

type CreateParams struct {
    Username     string `json:"username"`
    Guests       Guests `json:"guests"`
    RoomType     string `json:"roomType"`
    CheckinDate  string `json:"checkinDate"`
    CheckoutDate string `json:"checkoutDate"`
}

type Guests struct {
    Person []Person `json:"person"`
}

type Person struct {
    Firstname string `json:"firstname"`
    Lastname  string `json:"lastname"`
}

func main() {
    r := gin.New()
    r.POST("/", func(c *gin.Context) {
        var f CreateParams
        if err := c.BindJSON(&f); err != nil {
            return
        }
        c.IndentedJSON(http.StatusOK, f)
    })
    r.Run(":4000")
}

run the server

go run /tmp/test.go

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /                         --> main.main.func1 (1 handlers)
[GIN-debug] Listening and serving HTTP on :4000

access from curl

curl 0:4000 -X POST -d '{"username":"foo","guests":{"person":[{"firstname":"foobar","lastname":"barfoo"},{"firstname":"foofoo","lastname":"barbar"}]}}'

{
    "username": "foo",
    "guests": {
        "person": [
            {
                "firstname": "foobar",
                "lastname": "barfoo"
            },
            {
                "firstname": "foofoo",
                "lastname": "barbar"
            }
        ]
    },
    "roomType": "",
    "checkinDate": "",
    "checkoutDate": ""
}

@delphinus I am not able to validate required field. Lets say for Firstname within Person.

type Person struct {
    Firstname string `json:"firstname" binding:"required"`
    Lastname  string `json:"lastname"`
}

Is there any way, we can validate?

@mustaqeem It seems a limitation of go-validator v10 itself (not derives from gin).

// NOTE: The original go-validator uses `validate` for its tag (not `binding`).

type CreateParams struct {
    // This works good here.
    Username     string `json:"username" validate:"required"`
    Guests       Guests `json:"guests"`
    // Also work.
    RoomType     string `json:"roomType" validate:"required"`
    CheckinDate  string `json:"checkinDate"`
    CheckoutDate string `json:"checkoutDate"`
}

type Guests struct {
    Person []Person `json:"person"`
}

type Person struct {
    // But this does not work.
    Firstname string `json:"firstname" validate:"required"`
    Lastname  string `json:"lastname"`
}

So, you need to validate the Person's manually.

if err := validate.Struct(v); err != nil {
    fmt.Printf("validation1:\n%+v\n\n", err)
}

for _, p := range v.Guests.Person {
    if err := validate.Struct(p); err != nil {
        fmt.Printf("validation2\n:%+v\n\n", err)
    }
}

output:

validation1:
Key: 'CreateParams.Username' Error:Field validation for 'Username' failed on the 'required' tag
Key: 'CreateParams.RoomType' Error:Field validation for 'RoomType' failed on the 'required' tag

validation2:
Key: 'Person.Firstname' Error:Field validation for 'Firstname' failed on the 'required' tag

Full example is here → https://github.com/delphinus/go-gin-issue-715

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iiinsomnia picture iiinsomnia  Â·  3Comments

ghost picture ghost  Â·  3Comments

frederikhors picture frederikhors  Â·  3Comments

oryband picture oryband  Â·  3Comments

wangcn picture wangcn  Â·  3Comments