user16368243
user16368243

Reputation:

How to extract comma separated values from query parameter in Go?

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?

Upvotes: 5

Views: 6745

Answers (4)

Gopher
Gopher

Reputation: 751

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"]

Upvotes: 3

blackgreen
blackgreen

Reputation: 44697

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]
}

Upvotes: 0

Sergei Ten
Sergei Ten

Reputation: 34

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

Upvotes: -1

Trock
Trock

Reputation: 636

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(),
    })
}

Upvotes: 1

Related Questions