-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add examples + godoc + updated README
- Loading branch information
joeybloggs
authored and
joeybloggs
committed
May 31, 2016
1 parent
492703a
commit c91ccaf
Showing
5 changed files
with
421 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,194 @@ | ||
# form | ||
form to struct decoder | ||
Package form | ||
============ | ||
<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg"> | ||
 | ||
[](https://godoc.org/github.com/go-playground/form) | ||
 | ||
|
||
Package form parses url.Values and fills a struct with values, creating objects as necessary. | ||
|
||
It has the following features: | ||
|
||
- Primitives types cause zero allocations. | ||
- Supports map of almost all types. | ||
- Supports both Numbered and Normal arrays i.e. "Array[0]" and just "Array" with multiple values passed. | ||
- Allows for Custom Type registration. | ||
- Handles time.Time using RFC3339 time format by default, but can easily be changes usings registering a Custom Type. | ||
|
||
Common Questions | ||
|
||
- Does it support encoding.TextUnmarshaler? No because TextUnmarshaler only accepts []byte but posted values can have multiple values, so is not suitable. | ||
|
||
Supported Types ( out of the box ) | ||
---------- | ||
|
||
* `string` | ||
* `bool` | ||
* `int`, `int8`, `int16`, `int32`, `int64` | ||
* `uint`, `uint8`, `uint16`, `uint32`, `uint64` | ||
* `float32`, `float64` | ||
* `struct` and `anonymous struct` | ||
* `interface{}` | ||
* `time.Time` - by default using RFC3339 | ||
* a `pointer` to one of the above types | ||
* `slice`, `array` | ||
* `map` | ||
* `custom types` can override any of the above types | ||
* many other types may be supported inherently (i.e. bson.ObjectId is type ObjectId string, which will get populated by the string type | ||
|
||
**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" | ||
|
||
Usage | ||
----- | ||
|
||
- Use symbol `.` for separating fields/structs. (i.e. `structfield.field`) | ||
- Use `[index or key]` for access to index of a slice/array or key for map. (i.e. `arrayfield[0]`, `mapfield[keyvalue]`) | ||
|
||
```html | ||
<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> | ||
``` | ||
|
||
Example | ||
------- | ||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/url" | ||
|
||
"github.com/go-playground/form" | ||
) | ||
|
||
// 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"}, | ||
} | ||
} | ||
``` | ||
|
||
Registering Custom Types | ||
-------------- | ||
```go | ||
decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) { | ||
return time.Parse("2006-01-02", vals[0]) | ||
}, time.Time{}) | ||
``` | ||
|
||
Benchmarks | ||
------ | ||
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.6.2 darwin/amd64 | ||
|
||
NOTE: the 1 allocationand B/op in the first 4 is actually the struct allocating when passing it in, so primitives are actually zero allocation. | ||
|
||
```go | ||
go test -bench=. -benchmem=true | ||
|
||
PASS | ||
BenchmarkSimpleUserStruct-8 5000000 320 ns/op 64 B/op 1 allocs/op | ||
BenchmarkSimpleUserStructParallel-8 20000000 110 ns/op 64 B/op 1 allocs/op | ||
BenchmarkPrimitivesStructAllPrimitivesTypes-8 1000000 1075 ns/op 96 B/op 1 allocs/op | ||
BenchmarkPrimitivesStructAllPrimitivesTypesParallel-8 5000000 285 ns/op 96 B/op 1 allocs/op | ||
BenchmarkComplexArrayStructAllTypes-8 100000 19748 ns/op 6600 B/op 143 allocs/op | ||
BenchmarkComplexArrayStructAllTypesParallel-8 300000 5844 ns/op 6600 B/op 143 allocs/op | ||
BenchmarkComplexMapStructAllTypes-8 50000 33943 ns/op 20276 B/op 224 allocs/op | ||
BenchmarkComplexMapStructAllTypesParallel-8 200000 12228 ns/op 20277 B/op 224 allocs/op | ||
BenchmarkArrayMapNestedStruct-8 300000 4658 ns/op 1840 B/op 27 allocs/op | ||
BenchmarkArrayMapNestedStructParallel-8 1000000 1498 ns/op 1840 B/op 27 allocs/op | ||
``` | ||
|
||
|
||
Complimentary Software | ||
---------------------- | ||
|
||
Here is a list of software that compliments using this library post decoding. | ||
|
||
* [Validator](https://github.com/go-playground/validator) - Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving. | ||
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags. | ||
|
||
Package Versioning | ||
---------- | ||
I'm jumping on the vendoring bandwagon, you should vendor this package as I will not | ||
be creating different version with gopkg.in like allot of my other libraries. | ||
|
||
Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE, | ||
it is so freeing not to worry about it and will help me keep pouring out bigger and better | ||
things for you the community. | ||
|
||
License | ||
------ | ||
Distributed under MIT License, please see license file in code for more details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
Package form parses url.Values and fills a struct with values, creating objects as necessary. | ||
It has the following features: | ||
- Primitives types cause zero allocations. | ||
- Supports map of almost all types. | ||
- Supports both Numbered and Normal arrays i.e. "Array[0]" and just "Array" with multiple values passed. | ||
- Allows for Custom Type registration. | ||
- Handles time.Time using RFC3339 time format by default, but can easily be changes usings registering a Custom Type. | ||
Common Questions | ||
Questions | ||
Does it support encoding.TextUnmarshaler? | ||
No because TextUnmarshaler only accepts []byte but posted values can have multiple values, so is not suitable. | ||
Supported Types | ||
out of the box supported types | ||
- string | ||
- bool | ||
- int, int8, int16, int32, int64 | ||
- uint, uint8, uint16, uint32, uint64 | ||
- float32, float64 | ||
- struct and anonymous struct | ||
- interface{} | ||
- time.Time` - by default using RFC3339 | ||
- a `pointer` to one of the above types | ||
- slice, array | ||
- map | ||
- `custom types` can override any of the above types | ||
- many other types may be supported inherently (i.e. bson.ObjectId is type ObjectId string, | ||
which will get populated by the string type | ||
**NOTE**: map, struct and slice nesting are ad infinitum. | ||
Usage | ||
symbols | ||
- Use symbol `.` for separating fields/structs. (i.e, `structfield.field`) | ||
- Use `[index or key]` for access to index of a slice/array or key for map. | ||
(i.e, `arrayfield[0]`, `mapfield[keyvalue]`) | ||
html | ||
<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> | ||
Example | ||
example parsing the above HTML | ||
package main | ||
import ( | ||
"fmt" | ||
"log" | ||
"net/url" | ||
"github.com/go-playground/form" | ||
) | ||
// 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"}, | ||
} | ||
} | ||
Registering Custom Types | ||
can easily register custom types. | ||
decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) { | ||
return time.Parse("2006-01-02", vals[0]) | ||
}, time.Time{}) | ||
*/ | ||
package form |
Oops, something went wrong.