While making my way through the excellent xanzy/go-gitlab library I stumbled across little gem I hadn’t seen before: google/go-querystring, a library that can marshal a struct into a URL querystring.
package main
import (
"fmt"
"github.com/google/go-querystring/query"
)
type QueryOptions struct {
Size int `url:"size,omitempty"`
Filter string `url:"filter,omitempty"`
ShouldBeIgnored int `url:"-"`
}
func main() {
o := QueryOptions{
Size: 100,
Filter: "all",
ShouldBeIgnored: 1000,
}
vals, _ := query.Values(o)
fmt.Println(vals.Encode())
// Output:
// filter=all&size=100
}
As with things like enocding/json
you can customize the marshalling process by attaching tags to the struct you want to convert into url.Values
. The example above shows the use of the url
tag including the omitempty
option. If you don’t want to include a field in the output, you can use the -
value for the url
tag.
Times
A common use-case is that you want to encode a time.Time
instance as part of a querystring. By default, encoding will use RFC3339 as format. If you want to change that to follow a custom layout, you can use the layout
tag:
package main
import (
"fmt"
"time"
"github.com/google/go-querystring/query"
)
type QueryOptions struct {
CreatedAt time.Time `layout:"2006-01-02"`
}
func main() {
o := QueryOptions{
CreatedAt: time.Now(),
}
vals, _ := query.Values(o)
fmt.Println(vals.Encode())
// Output:
// CreatedAt=2021-10-31
}
In case you just want to get a timestamp out of it, use url:",unix"
, url:",unixmilli"
or url:",unixnano"
.
Slices
Slices/arrays in querystrings are always something weird. Some webframeworks expect them to be sent using special keys like param[]=1¶m[]=2
, while others expect just a single value that is then separated with spaces or commas or even just the normal parameter name just repeated. go-querystring has some flags for most of these.
Let’s assume that you have this kind of struct:
type Data struct {
Values []string
}
… and then assign a simple array with three strings to it (["a", "b", "c"]
). Depending on the formatting option, you’d get the following output:
// default (no option)
Values=a&Values=b&Values=c
// numbered
Values0=a&Values1=b&Values2=c
// space
Values=a+b+c
// comma
Values=a%2Cb%2Cc
// brackets
Values%5B%5D=a&Values%5B%5D=b&Values%5B%5D=c
Booleans
By default, booleans are encoded as true
or false
but you can change that by setting the int
option inside the url
tag:
package main
import (
"fmt"
"net/url"
"github.com/google/go-querystring/query"
)
type Data struct {
Flag bool `url:",int"`
}
func main() {
var vals url.Values
vals, _ = query.Values(Data{Flag: true})
fmt.Println(vals.Encode())
// Output:
// Flag=1
}
Custom values
When you have custom data structures inside your main struct, then you’ll most likely want to also have some kind of custom encoding for it. This can be done by having the struct implement the query.Encoder
interface:
type Encoder interface {
EncodeValues(key string, v *url.Values) error
}
package main
import (
"fmt"
"net/url"
"github.com/google/go-querystring/query"
)
type CustomField struct{}
func (v *CustomField) EncodeValues(key string, values *url.Values) error {
values.Set(key, "CUSTOM")
return nil
}
type Data struct {
Flag *CustomField
}
func main() {
var vals url.Values
vals, _ = query.Values(Data{Flag: &CustomField{}})
fmt.Println(vals.Encode())
// Output:
// Flag=CUSTOM
}
Conclusion
That’s pretty much every use-case I’ve run into so far with go-querystring and I really liked the way it allowed me to handle them! There are also some options that I haven’t mention here simply because I haven’t used them yet. You can find all of them in the documentation for the query.Values
function.
Do you want to give me feedback about this article in private? Please send it to comments@zerokspot.com.
Alternatively, this website also supports Webmentions. If you write a post on a blog that supports this technique, I should get notified about your link π