Package form Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values.
It has the following features:
"Array[0]"
and just "Array"
with multiple values passed.array
or map
values are passed down, the array
and map
are left as their default values in the struct.Common Questions
array/slice
with array[idx]/slice[idx]
, in which order are they parsed? array/slice
then array[idx]/slice[idx]
string
bool
int
, int8
, int16
, int32
, int64
uint
, uint8
, uint16
, uint32
, uint64
float32
, float64
struct
and anonymous struct
interface{}
time.Time
- by default using RFC3339pointer
to one of the above typesslice
, array
map
custom types
can override any of the above typesNOTE: map
, struct
and slice
nesting are ad infinitum.
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"
.
for separating fields/structs. (eg. structfield.field
)[index or key]
for access to index of a slice/array or key for map. (eg. arrayfield[0]
, mapfield[keyvalue]
)<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>
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) }
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{})
you can tell form to ignore fields using -
in the tag
type MyStruct struct { Field string `form:"-"` }
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"` }
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"}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
Here is a list of software that compliments using this library post decoding.
Distributed under MIT License, please see license file in code for more details.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4