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