Home

Awesome

Package form

<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">Project status Build Status Coverage Status Go Report Card GoDoc License Gitter

Package form Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values.

It has the following features:

Common Questions

Supported Types ( out of the box )

NOTE: map, struct and slice nesting are ad infinitum.

Installation

Use go get.

go get github.com/go-playground/form

Then import the form package into your own code.

import "github.com/go-playground/form/v4"

Usage

<form method="POST">
  <input type="text" name="Name" value="joeybloggs"/>
  <input type="text" name="Age" value="3"/>
  <input type="text" name="Gender" value="Male"/>
  <input type="text" name="Address[0].Name" value="26 Here Blvd."/>
  <input type="text" name="Address[0].Phone" value="9(999)999-9999"/>
  <input type="text" name="Address[1].Name" value="26 There Blvd."/>
  <input type="text" name="Address[1].Phone" value="1(111)111-1111"/>
  <input type="text" name="active" value="true"/>
  <input type="text" name="MapExample[key]" value="value"/>
  <input type="text" name="NestedMap[key][key]" value="value"/>
  <input type="text" name="NestedArray[0][0]" value="value"/>
  <input type="submit"/>
</form>

Examples

Decoding

package main

import (
	"fmt"
	"log"
	"net/url"

	"github.com/go-playground/form/v4"
)

// Address contains address information
type Address struct {
	Name  string
	Phone string
}

// User contains user information
type User struct {
	Name        string
	Age         uint8
	Gender      string
	Address     []Address
	Active      bool `form:"active"`
	MapExample  map[string]string
	NestedMap   map[string]map[string]string
	NestedArray [][]string
}

// use a single instance of Decoder, it caches struct info
var decoder *form.Decoder

func main() {
	decoder = form.NewDecoder()

	// this simulates the results of http.Request's ParseForm() function
	values := parseForm()

	var user User

	// must pass a pointer
	err := decoder.Decode(&user, values)
	if err != nil {
		log.Panic(err)
	}

	fmt.Printf("%#v\n", user)
}

// this simulates the results of http.Request's ParseForm() function
func parseForm() url.Values {
	return url.Values{
		"Name":                []string{"joeybloggs"},
		"Age":                 []string{"3"},
		"Gender":              []string{"Male"},
		"Address[0].Name":     []string{"26 Here Blvd."},
		"Address[0].Phone":    []string{"9(999)999-9999"},
		"Address[1].Name":     []string{"26 There Blvd."},
		"Address[1].Phone":    []string{"1(111)111-1111"},
		"active":              []string{"true"},
		"MapExample[key]":     []string{"value"},
		"NestedMap[key][key]": []string{"value"},
		"NestedArray[0][0]":   []string{"value"},
	}
}

Encoding

package main

import (
	"fmt"
	"log"

	"github.com/go-playground/form/v4"
)

// Address contains address information
type Address struct {
	Name  string
	Phone string
}

// User contains user information
type User struct {
	Name        string
	Age         uint8
	Gender      string
	Address     []Address
	Active      bool `form:"active"`
	MapExample  map[string]string
	NestedMap   map[string]map[string]string
	NestedArray [][]string
}

// use a single instance of Encoder, it caches struct info
var encoder *form.Encoder

func main() {
	encoder = form.NewEncoder()

	user := User{
		Name:   "joeybloggs",
		Age:    3,
		Gender: "Male",
		Address: []Address{
			{Name: "26 Here Blvd.", Phone: "9(999)999-9999"},
			{Name: "26 There Blvd.", Phone: "1(111)111-1111"},
		},
		Active:      true,
		MapExample:  map[string]string{"key": "value"},
		NestedMap:   map[string]map[string]string{"key": {"key": "value"}},
		NestedArray: [][]string{{"value"}},
	}

	// must pass a pointer
	values, err := encoder.Encode(&user)
	if err != nil {
		log.Panic(err)
	}

	fmt.Printf("%#v\n", values)
}

Registering Custom Types

Decoder

decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
	return time.Parse("2006-01-02", vals[0])
}, time.Time{})

ADDITIONAL: if a struct type is registered, the function will only be called if a url.Value exists for the struct and not just the struct fields eg. url.Values{"User":"Name%3Djoeybloggs"} will call the custom type function with 'User' as the type, however url.Values{"User.Name":"joeybloggs"} will not.

Encoder

encoder.RegisterCustomTypeFunc(func(x interface{}) ([]string, error) {
	return []string{x.(time.Time).Format("2006-01-02")}, nil
}, time.Time{})

Ignoring Fields

you can tell form to ignore fields using - in the tag

type MyStruct struct {
	Field string `form:"-"`
}

Omitempty

you can tell form to omit empty fields using ,omitempty or FieldName,omitempty in the tag

type MyStruct struct {
	Field  string `form:",omitempty"`
	Field2 string `form:"CustomFieldName,omitempty"`
}

Notes

To maximize compatibility with other systems the Encoder attempts to avoid using array indexes in url.Values if at all possible.

eg.

// A struct field of
Field []string{"1", "2", "3"}

// will be output a url.Value as
"Field": []string{"1", "2", "3"}

and not
"Field[0]": []string{"1"}
"Field[1]": []string{"2"}
"Field[2]": []string{"3"}

// however there are times where it is unavoidable, like with pointers
i := int(1)
Field []*string{nil, nil, &i}

// to avoid index 1 and 2 must use index
"Field[2]": []string{"1"}

Benchmarks

Run on M1 MacBook Pro using go version go1.20.6 darwin/amd64

NOTE: the 1 allocation and B/op in the first 4 decodes is actually the struct allocating when passing it in, so primitives are actually zero allocation.

go test -run=NONE -bench=. -benchmem=true ./...
goos: darwin
goarch: arm64
pkg: github.com/go-playground/form/v4/benchmarks
BenchmarkSimpleUserDecodeStruct-8                                8704111               121.1 ns/op            64 B/op          1 allocs/op
BenchmarkSimpleUserDecodeStructParallel-8                       35916134                32.89 ns/op           64 B/op          1 allocs/op
BenchmarkSimpleUserEncodeStruct-8                                3746173               320.7 ns/op           485 B/op         10 allocs/op
BenchmarkSimpleUserEncodeStructParallel-8                        7293147               180.0 ns/op           485 B/op         10 allocs/op
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8              2993259               400.5 ns/op            96 B/op          1 allocs/op
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8     13023300                97.70 ns/op           96 B/op          1 allocs/op
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8               643202              1767 ns/op            2977 B/op         35 allocs/op
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8      1000000              1202 ns/op            2978 B/op         35 allocs/op
BenchmarkComplexArrayDecodeStructAllTypes-8                       172630              6822 ns/op            2008 B/op        121 allocs/op
BenchmarkComplexArrayDecodeStructAllTypesParallel-8               719788              1735 ns/op            2009 B/op        121 allocs/op
BenchmarkComplexArrayEncodeStructAllTypes-8                       197052              5839 ns/op            7087 B/op        104 allocs/op
BenchmarkComplexArrayEncodeStructAllTypesParallel-8               348039              3247 ns/op            7089 B/op        104 allocs/op
BenchmarkComplexMapDecodeStructAllTypes-8                         139246              8550 ns/op            5313 B/op        130 allocs/op
BenchmarkComplexMapDecodeStructAllTypesParallel-8                 409018              3143 ns/op            5317 B/op        130 allocs/op
BenchmarkComplexMapEncodeStructAllTypes-8                         208833              5515 ns/op            4257 B/op        103 allocs/op
BenchmarkComplexMapEncodeStructAllTypesParallel-8                 523833              2182 ns/op            4258 B/op        103 allocs/op
BenchmarkDecodeNestedStruct-8                                     807690              1408 ns/op             344 B/op         14 allocs/op
BenchmarkDecodeNestedStructParallel-8                            3409441               359.6 ns/op           344 B/op         14 allocs/op
BenchmarkEncodeNestedStruct-8                                    1488520               803.6 ns/op           653 B/op         16 allocs/op
BenchmarkEncodeNestedStructParallel-8                            3570204               346.6 ns/op           653 B/op         16 allocs/op

Competitor benchmarks can be found here

Complimentary Software

Here is a list of software that compliments using this library post decoding.

License

Distributed under MIT License, please see license file in code for more details.