« Back to Index

Go: form encoding with various packages

View original Gist on GitHub

Tags: #go #serialization

1. form encoding.go

package main

import (
	"bytes"
	"fmt"
	"net/url"

	"github.com/ajg/form"
	"github.com/google/go-querystring/query" // NOTE: Only handles encoding, not decoding!
	"github.com/gorilla/schema"
	"github.com/pasztorpisti/qs"
	"github.com/hetiansu5/urlquery"
)

type A struct {
	Services []string `form:"services"`
}

type B struct {
	Services []string `url:"services,brackets,omitempty"` // needed the 'brackets' bit to make it work
}

type C struct {
	Services []string `schema:"services"`
}

type D struct {
	Services []string `qs:"services"`
}

type E struct {
	Services []string `query:"services"`
}

func main() {
	fmt.Println("we want: services[]=A&services[]=B\n")

	fmt.Println("ajg/form")
	a := A{Services: []string{"A", "B"}}
	buf := new(bytes.Buffer)
	form.NewEncoder(buf).DelimitWith('|').Encode(a)
	u, _ := url.QueryUnescape(buf.String())
	fmt.Println("❌", u)

	fmt.Println("google/go-querystring")
	b := B{Services: []string{"A", "B"}}
	v, _ := query.Values(b)
	fmt.Println("✅", v.Encode())
	u, _ = url.QueryUnescape(v.Encode())
	fmt.Println("✅", u)

	fmt.Println("gorilla/schema")
	c := C{Services: []string{"A", "B"}}
	encoder := schema.NewEncoder()
	form := url.Values{}
	encoder.Encode(c, form)
	fmt.Println("❌", form.Encode())

	fmt.Println("pasztorpisti/qs")
	d := D{Services: []string{"A", "B"}}
	s, _ := qs.Marshal(&d)
	fmt.Println("❌", s)

	fmt.Println("hetiansu5/urlquery")
	e := E{Services: []string{"A", "B"}}
	bytes, _ := urlquery.Marshal(e)
	fmt.Println("✅", string(bytes))
	u, _ = url.QueryUnescape(string(bytes))
	fmt.Println("✅", u)
}

2. ajg form custom encoding.go

package main

import (
	"bytes"
	"fmt"
	"net/url"

	"github.com/ajg/form"
)

type Compatibool bool

func (b Compatibool) MarshalText() ([]byte, error) {
	if b {
		return []byte("1"), nil
	}
	return []byte("0"), nil
}

type T bool

type options struct {
	Foo      string       `form:"foo,omitempty"`
	Int      int          `form:"integer,omitempty"`
	TeeP     *T           `form:"tp,omitempty"`
	Tee      T            `form:"t,omitempty"`
	Services []string     `form:"services,omitempty"`
	CP       *Compatibool `form:"cp,omitempty"`
	C        Compatibool  `form:"c,omitempty"`
}

func TBool(b bool) *T {
	t := T(b)
	return &t
}

func CBool(b bool) *Compatibool {
	c := Compatibool(b)
	return &c
}

func main() {
	var i interface{}
	i = options{Foo: "b", TeeP: TBool(true), Tee: T(true), Services: []string{"A", "B"}, CP: CBool(true), C: Compatibool(true)}

	buf := new(bytes.Buffer)
	form.NewEncoder(buf).KeepZeros(true).DelimitWith('|').Encode(i)
	u, _ := url.QueryUnescape(buf.String())
	fmt.Println(u)

	i = options{Foo: "b", TeeP: TBool(false), Tee: T(false), Services: []string{"A", "B"}, CP: CBool(false), C: Compatibool(false)}

	buf = new(bytes.Buffer)
	form.NewEncoder(buf).KeepZeros(true).DelimitWith('|').Encode(i)
	u, _ = url.QueryUnescape(buf.String())
	fmt.Println(u) // C is omitted as if it wasn't set.
}

3. go-querystring custom encoding.go

package main

import (
	"fmt"
	"net/url"

	"github.com/google/go-querystring/query"
)

type T bool

func (t T) EncodeValues(key string, v *url.Values) error {
	fmt.Printf("key: %+v, t: %+v, v: %+v\n", key, t, v)
	switch t {
	case true:
		v.Add(key, "1")
	case false:
		v.Add(key, "0")
	}
	return nil
}

type options struct {
	Foo  string `url:"foo,omitempty"`
	TeeP *T     `url:"tp,omitempty"`
	Tee  T      `url:"t,omitempty"`
}

func main() {
	fmt.Println("No TeeP or Tee set")
	v, _ := query.Values(options{Foo: "a"})
	fmt.Printf("v.Encode: %+v\n\n", v.Encode())

	fmt.Println("TeeP and Tee set to true")
	t := T(true)
	v, _ = query.Values(options{Foo: "b", TeeP: &t, Tee: T(true)})
	fmt.Printf("v.Encode: %+v\n\n", v.Encode())

	fmt.Println("TeeP and Tee set to false")
	t = T(false)
	v, _ = query.Values(options{Foo: "c", TeeP: &t, Tee: T(false)})
	fmt.Printf("v.Encode: %+v\n\n", v.Encode()) // NOTE: Tee doesn't cause EncodeValues() to be called?
}

/*
No TeeP or Tee set
v.Encode: foo=a

TeeP and Tee set to true
key: tp, t: true, v: &map[foo:[b]]
key: t, t: true, v: &map[foo:[b] tp:[1]]
v.Encode: foo=b&t=1&tp=1

TeeP and Tee set to false
key: tp, t: false, v: &map[foo:[c]]
v.Encode: foo=c&tp=0
*/

4. urlquery custom encoding.go

package main

import (
	"fmt"
	"net/url"

	"github.com/hetiansu5/urlquery"
)

// An OptionData is test structure
type OptionData struct {
	Services []string `query:"services,omitempty"`
	TeeP     *T       `query:"tp,omitempty"`
	Tee      T        `query:"t,omitempty"`
	Int      int      `query:"integer,omitempty"`
}

// A SelfQueryEncoder is test structure
type SelfQueryEncoder struct{}

// Escape is test func
func (u SelfQueryEncoder) Escape(s string) string {
	fmt.Println("Escape", s)
	return url.QueryEscape(s)
}

// UnEscape is test func
func (u SelfQueryEncoder) UnEscape(s string) (string, error) {
	fmt.Println("UnEscape", s)
	return url.QueryUnescape(s)
}

type T bool

func marshal(data OptionData) {
	fmt.Printf("%+v\n", data)

	builder := urlquery.NewEncoder(
		urlquery.WithNeedEmptyValue(true),
		urlquery.WithQueryEncoder(SelfQueryEncoder{}))
	bytes, err := builder.Marshal(data)
	if err != nil {
		fmt.Println(err)
		return
	}
	u, _ := url.QueryUnescape(string(bytes))
	fmt.Println(u)
}

func main() {
	marshal(OptionData{
		Services: []string{
			"A",
			"B",
		},
	})

	marshal(OptionData{
		Services: []string{
			"A",
			"B",
		},
		Int: 123,
	})

	marshal(OptionData{
		Services: []string{
			"A",
			"B",
		},
		Int: 0, // is omitted if we set WithNeedEmptyValue(true) but we don't WANT that option set because it means even if the field is not set by the user, it will have a default value set in the output
	})

	tp := T(true)
	marshal(OptionData{
		Services: []string{
			"A",
			"B",
		},
		TeeP: &tp,
		Tee:  T(true),
	})

	tp = T(false)
	marshal(OptionData{
		Services: []string{
			"A",
			"B",
		},
		TeeP: &tp,
		Tee:  T(false),
	})
}