3
votes

I am using the Gin Web Framework and I am trying to find a way to bind a list of comma separated values from a query parameter into a struct. The following is a snippet of my code:

type QueryParams struct {
    Type            []string `form:"type"`
}


func BulkRead(c *gin.Context) {

    params := QueryParams{
        Type:            []string{},
    }

    if err := c.ShouldBindQuery(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't bind query params"})
        return
    }
    
    c.Status(200)
}

Request: GET /api/v1/car?type=ford,audi

What I expect: ["ford", "audi"]

What I am getting: "ford,audi"

Is there an easy way to do this? Or will I need to write a custom function to handle this?

4
I think you'd probably need to use GetQueryArray.Clark McCauley
I gave that a try but it still does not give me the result I am looking for, arr, _ := c.GetQueryArray("type"); fmt.Println(arr[0]) -> "ford,audi"cincip
The function has no way to know you are expecting multiple values splitted by comma (why not colon or other char?). Maybe the following request will work automatically in any http router: GET /api/v1/cart?type=ford&type=audiFulldump
Maybe rsql is a more suitable solution for your APIFulldump

4 Answers

2
votes

To extract comma separated values from query parameter you can make use of Split() method from strings package .I have created a simple program for your scenario as follows :

package main

import (
    "fmt"
    "strconv"
    "strings"
)

func main() {

    var cars []string
    qryResult := "ford,audi"
    carBrands := strings.Split(qryResult, ",")
    fmt.Println(carBrands)

    for i := 0; i < len(carBrands); i++ {

        cars = append(cars, strconv.Quote(carBrands[i]))
    }

    fmt.Println(cars)

}

Output:

[ford audi]
["ford" "audi"]
0
votes

It might be helpful for you too.

package main

import (
    "log"
    "net/http"
    "strings"

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

type TypeBinding struct {
    name string
}

func NewTypeBinding(name string) *TypeBinding {
    return &TypeBinding{
        name: name,
    }
}

func (t *TypeBinding) Name() string {
    return t.name
}

func (t *TypeBinding) Bind(r *http.Request, i interface{}) error {
    ttype := r.URL.Query().Get(t.name)

    ii := i.(*QueryParams)

    ii.Type = strings.Split(ttype, ",")

    return nil
}

type QueryParams struct {
    Type []string `url:"type"`
}

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

    typeBinding := NewTypeBinding("type")
    var opt QueryParams

    router.GET("/", func(c *gin.Context) {
        if err := c.MustBindWith(&opt, typeBinding); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't bind query params"})
            return
        }

        log.Printf("type: %v", opt.Type)
        c.String(http.StatusOK, "type: %s", opt.Type)
    })
    router.Run(":8080")
}
0
votes

Gin doesn't have a way to do this for you. The easiest solution is to just get the query param and split it yourself. This is literally 2 lines of code:

func MyHandler(c *gin.Context) {
    ss := strings.Split(c.Query("type"), ",")
    fmt.Println(ss) // [ford audi]

    qp := QueryParams{
        Type: ss,
    }
}

If you have the option to change how the request is made, change it to:

GET /api/v1/car?type=ford&type=audi (repeat the query key)

or URL-encode the comma:

GET /api/v1/car?type=ford%20audi (, -> %20)

Then context.GetQueryArray will work as you expect:

func MyHandler(c *gin.Context) {
    cars, _ := c.GetQueryArray("type")
    fmt.Println(cars) // [ford audi]
}

Of course implementing your own binding.Binding is an option, suited if you have more than just that query parameter and want to encapsulate the binding logic into one place, but I feel it's overkill for your use case:

type commaSepQueryBinding struct {}

func (commaSepQueryBinding) Name() string {
    return "comma-sep-query"
}

func (commaSepQueryBinding) Bind(req *http.Request, obj interface{}) error {
    values := req.URL.Query()
    p := obj.(*QueryParams)
    p.Type = strings.Split(values["type"][0], ",")
    return nil
}

func MyHandler(c *gin.Context) {
    q := QueryParams{}
    err := c.ShouldBindWith(&q, &commaSepQueryBinding{})
    if err != nil {
        panic(err)
    }
    fmt.Println(q.Type) // [ford audi]
}
-1
votes

There may not be such a way. Usually I do this:

type QueryParams struct {
    Type string `form:"type"`
}

func (q *QueryParams) Types() []string {
    return strings.Split(q.Type, ",")
}

func BulkRead(c *gin.Context) {
    params := new(QueryParams)

    if err := c.ShouldBindQuery(&params); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't bind query params"})
        return
    }
    c.JSONP(200, map[string]interface{}{
        "types": params.Types(),
    })
}