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 :)

Comment From: javierprovecho

@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")
}

Comment From: RAndrews137

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.

Comment From: fabulousduck

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
}

Comment From: delphinus

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": ""
}

Comment From: mustaqeem

@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?

Comment From: delphinus

@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

Comment From: kszafran

@delphinus @mustaqeem Sorry for resurrecting this, but maybe it'll help someone. If you want to descend into slices/maps and validate their values you need to add the dive validation. This should work:

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

Comment From: crapthings

package main

import ("fmt")
import "github.com/gin-gonic/gin"

type User struct {
  _id int `json:"_id"`
  fullname string `json:"fullname"`
}

func createUser (idx int) (user User) {
  user._id = idx
  user.fullname = "test"
  return user
}

func main () {
  users := []User{}

  for i := 1; i <= 10; i++ {
    users = append(users, createUser(i))
  }

  fmt.Println(users)

  router := gin.Default()

  router.GET("/", func (c *gin.Context) {
    c.String(200, "go with gin")
  })

  api := router.Group("/api")
  {
    api.GET("/users", func (c *gin.Context) {
      fmt.Println(users)
      c.IndentedJSON(200, users)
    })
  }

  router.Run(":3000")
}

Gin Question:  How bind JSON array

I'm getting an empty array with struct, what is wrong?

Comment From: kszafran

@crapthings You need to export the struct fields:

type User struct {
  ID int `json:"_id"`
  Fullname string `json:"fullname"`
}

Comment From: crapthings

@kszafran ok thanks~

should use the uppercase in struct

Comment From: yanngit

@delphinus @mustaqeem Sorry for resurrecting this, but maybe it'll help someone. If you want to descend into slices/maps and validate their values you need to add the dive validation. This should work:

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

You are my hero !!! Thanks

Comment From: RiansyahTohamba

you can bindJson with array like this:

type UserRequest struct {
    Id   string `json:"_id"`
    Name string `json:"name"`
}

func PostHandler(c *gin.Context) {
    var req []UserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": "invalid body request"})
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "name": "api user ",
        "data": req,
    })
}

Comment From: lylest

Simplifying MongoDB Array of Objects Update in Go with Gin

Hi there,

I recently faced a challenging task of updating an array of objects in my MongoDB document using Go with Gin. The document structure looked like this:

{
  "permissions": [
    {
      "id": 0,
      "name": "users",
      "list": []
    },
    {
      "id": 1,
      "name": "customers",
      "list": []
    }
  ]
}

Finding a solution was a bit of a headache, and I tried several approaches. Eventually, I found a clean and effective solution. Here's how I did it:

Document Structure:

type PermissionsList struct {
    PermissionList []Permission `json:"permissionList" bson:"permissionList"`
}

type Permission struct {
    ID   interface{} `json:"id" bson:"id"` 
    Name string      `json:"name" bson:"name"`
    List []string    `json:"list" bson:"list"`
}

Decode JSON and Insert:

var permissions = new(models.PermissionsList)
decodeErr := utils.DecodeJSON(context, permissions)
if decodeErr != nil {
    return decodeErr, "Failed to decode body", nil, 500
}

Update MongoDB Document:

idErr, objectId := utils.ToObjectId(id)
if idErr != nil {
    return idErr, "Invalid user id", nil, 422
}

opts := options.FindOneAndUpdate().SetUpsert(true)
filter := bson.D{{"_id", objectId}}
update := bson.D{
    {
        "$set",
        bson.D{
            {"permissions", permissions.PermissionList},
        },
    },
}

This structure worked seamlessly for updating the MongoDB array of objects. Feel free to use it in your projects or adapt it to fit your specific needs.