README.md (12205B)
1 Package form 2 ============ 3 <img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">![Project status](https://img.shields.io/badge/version-4.2.0-green.svg) 4 [![Build Status](https://github.com/go-playground/form/actions/workflows/workflow.yml/badge.svg)](https://github.com/go-playground/form/actions/workflows/workflow.yml) 5 [![Coverage Status](https://coveralls.io/repos/github/go-playground/form/badge.svg?branch=master)](https://coveralls.io/github/go-playground/form?branch=master) 6 [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/form)](https://goreportcard.com/report/github.com/go-playground/form) 7 [![GoDoc](https://godoc.org/github.com/go-playground/form?status.svg)](https://godoc.org/github.com/go-playground/form) 8 ![License](https://img.shields.io/dub/l/vibe-d.svg) 9 [![Gitter](https://badges.gitter.im/go-playground/form.svg)](https://gitter.im/go-playground/form?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 10 11 Package form Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. 12 13 It has the following features: 14 15 - Supports map of almost all types. 16 - Supports both Numbered and Normal arrays eg. `"Array[0]"` and just `"Array"` with multiple values passed. 17 - Slice honours the specified index. eg. if "Slice[2]" is the only Slice value passed down, it will be put at index 2; if slice isn't big enough it will be expanded. 18 - Array honours the specified index. eg. if "Array[2]" is the only Array value passed down, it will be put at index 2; if array isn't big enough a warning will be printed and value ignored. 19 - Only creates objects as necessary eg. if no `array` or `map` values are passed down, the `array` and `map` are left as their default values in the struct. 20 - Allows for Custom Type registration. 21 - Handles time.Time using RFC3339 time format by default, but can easily be changed by registering a Custom Type, see below. 22 - Handles Encoding & Decoding of almost all Go types eg. can Decode into struct, array, map, int... and Encode a struct, array, map, int... 23 24 Common Questions 25 26 - Does it support encoding.TextUnmarshaler? No because TextUnmarshaler only accepts []byte but posted values can have multiple values, so is not suitable. 27 - Mixing `array/slice` with `array[idx]/slice[idx]`, in which order are they parsed? `array/slice` then `array[idx]/slice[idx]` 28 29 Supported Types ( out of the box ) 30 ---------- 31 32 * `string` 33 * `bool` 34 * `int`, `int8`, `int16`, `int32`, `int64` 35 * `uint`, `uint8`, `uint16`, `uint32`, `uint64` 36 * `float32`, `float64` 37 * `struct` and `anonymous struct` 38 * `interface{}` 39 * `time.Time` - by default using RFC3339 40 * a `pointer` to one of the above types 41 * `slice`, `array` 42 * `map` 43 * `custom types` can override any of the above types 44 * many other types may be supported inherently 45 46 **NOTE**: `map`, `struct` and `slice` nesting are ad infinitum. 47 48 Installation 49 ------------ 50 51 Use go get. 52 53 go get github.com/go-playground/form 54 55 Then import the form package into your own code. 56 57 import "github.com/go-playground/form/v4" 58 59 Usage 60 ----- 61 62 - Use symbol `.` for separating fields/structs. (eg. `structfield.field`) 63 - Use `[index or key]` for access to index of a slice/array or key for map. (eg. `arrayfield[0]`, `mapfield[keyvalue]`) 64 65 ```html 66 <form method="POST"> 67 <input type="text" name="Name" value="joeybloggs"/> 68 <input type="text" name="Age" value="3"/> 69 <input type="text" name="Gender" value="Male"/> 70 <input type="text" name="Address[0].Name" value="26 Here Blvd."/> 71 <input type="text" name="Address[0].Phone" value="9(999)999-9999"/> 72 <input type="text" name="Address[1].Name" value="26 There Blvd."/> 73 <input type="text" name="Address[1].Phone" value="1(111)111-1111"/> 74 <input type="text" name="active" value="true"/> 75 <input type="text" name="MapExample[key]" value="value"/> 76 <input type="text" name="NestedMap[key][key]" value="value"/> 77 <input type="text" name="NestedArray[0][0]" value="value"/> 78 <input type="submit"/> 79 </form> 80 ``` 81 82 Examples 83 ------- 84 85 Decoding 86 ```go 87 package main 88 89 import ( 90 "fmt" 91 "log" 92 "net/url" 93 94 "github.com/go-playground/form/v4" 95 ) 96 97 // Address contains address information 98 type Address struct { 99 Name string 100 Phone string 101 } 102 103 // User contains user information 104 type User struct { 105 Name string 106 Age uint8 107 Gender string 108 Address []Address 109 Active bool `form:"active"` 110 MapExample map[string]string 111 NestedMap map[string]map[string]string 112 NestedArray [][]string 113 } 114 115 // use a single instance of Decoder, it caches struct info 116 var decoder *form.Decoder 117 118 func main() { 119 decoder = form.NewDecoder() 120 121 // this simulates the results of http.Request's ParseForm() function 122 values := parseForm() 123 124 var user User 125 126 // must pass a pointer 127 err := decoder.Decode(&user, values) 128 if err != nil { 129 log.Panic(err) 130 } 131 132 fmt.Printf("%#v\n", user) 133 } 134 135 // this simulates the results of http.Request's ParseForm() function 136 func parseForm() url.Values { 137 return url.Values{ 138 "Name": []string{"joeybloggs"}, 139 "Age": []string{"3"}, 140 "Gender": []string{"Male"}, 141 "Address[0].Name": []string{"26 Here Blvd."}, 142 "Address[0].Phone": []string{"9(999)999-9999"}, 143 "Address[1].Name": []string{"26 There Blvd."}, 144 "Address[1].Phone": []string{"1(111)111-1111"}, 145 "active": []string{"true"}, 146 "MapExample[key]": []string{"value"}, 147 "NestedMap[key][key]": []string{"value"}, 148 "NestedArray[0][0]": []string{"value"}, 149 } 150 } 151 ``` 152 153 Encoding 154 ```go 155 package main 156 157 import ( 158 "fmt" 159 "log" 160 161 "github.com/go-playground/form/v4" 162 ) 163 164 // Address contains address information 165 type Address struct { 166 Name string 167 Phone string 168 } 169 170 // User contains user information 171 type User struct { 172 Name string 173 Age uint8 174 Gender string 175 Address []Address 176 Active bool `form:"active"` 177 MapExample map[string]string 178 NestedMap map[string]map[string]string 179 NestedArray [][]string 180 } 181 182 // use a single instance of Encoder, it caches struct info 183 var encoder *form.Encoder 184 185 func main() { 186 encoder = form.NewEncoder() 187 188 user := User{ 189 Name: "joeybloggs", 190 Age: 3, 191 Gender: "Male", 192 Address: []Address{ 193 {Name: "26 Here Blvd.", Phone: "9(999)999-9999"}, 194 {Name: "26 There Blvd.", Phone: "1(111)111-1111"}, 195 }, 196 Active: true, 197 MapExample: map[string]string{"key": "value"}, 198 NestedMap: map[string]map[string]string{"key": {"key": "value"}}, 199 NestedArray: [][]string{{"value"}}, 200 } 201 202 // must pass a pointer 203 values, err := encoder.Encode(&user) 204 if err != nil { 205 log.Panic(err) 206 } 207 208 fmt.Printf("%#v\n", values) 209 } 210 ``` 211 212 Registering Custom Types 213 -------------- 214 215 Decoder 216 ```go 217 decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) { 218 return time.Parse("2006-01-02", vals[0]) 219 }, time.Time{}) 220 ``` 221 ADDITIONAL: if a struct type is registered, the function will only be called if a url.Value exists for 222 the struct and not just the struct fields eg. url.Values{"User":"Name%3Djoeybloggs"} will call the 223 custom type function with 'User' as the type, however url.Values{"User.Name":"joeybloggs"} will not. 224 225 226 Encoder 227 ```go 228 encoder.RegisterCustomTypeFunc(func(x interface{}) ([]string, error) { 229 return []string{x.(time.Time).Format("2006-01-02")}, nil 230 }, time.Time{}) 231 ``` 232 233 Ignoring Fields 234 -------------- 235 you can tell form to ignore fields using `-` in the tag 236 ```go 237 type MyStruct struct { 238 Field string `form:"-"` 239 } 240 ``` 241 242 Omitempty 243 -------------- 244 you can tell form to omit empty fields using `,omitempty` or `FieldName,omitempty` in the tag 245 ```go 246 type MyStruct struct { 247 Field string `form:",omitempty"` 248 Field2 string `form:"CustomFieldName,omitempty"` 249 } 250 ``` 251 252 Notes 253 ------ 254 To maximize compatibility with other systems the Encoder attempts 255 to avoid using array indexes in url.Values if at all possible. 256 257 eg. 258 ```go 259 // A struct field of 260 Field []string{"1", "2", "3"} 261 262 // will be output a url.Value as 263 "Field": []string{"1", "2", "3"} 264 265 and not 266 "Field[0]": []string{"1"} 267 "Field[1]": []string{"2"} 268 "Field[2]": []string{"3"} 269 270 // however there are times where it is unavoidable, like with pointers 271 i := int(1) 272 Field []*string{nil, nil, &i} 273 274 // to avoid index 1 and 2 must use index 275 "Field[2]": []string{"1"} 276 ``` 277 278 Benchmarks 279 ------ 280 ###### Run on MacBook Pro (15-inch, 2017) using go version go1.10.1 darwin/amd64 281 282 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. 283 284 ```go 285 go test -run=NONE -bench=. -benchmem=true 286 goos: darwin 287 goarch: amd64 288 pkg: github.com/go-playground/form/benchmarks 289 290 BenchmarkSimpleUserDecodeStruct-8 5000000 236 ns/op 64 B/op 1 allocs/op 291 BenchmarkSimpleUserDecodeStructParallel-8 20000000 82.1 ns/op 64 B/op 1 allocs/op 292 BenchmarkSimpleUserEncodeStruct-8 2000000 627 ns/op 485 B/op 10 allocs/op 293 BenchmarkSimpleUserEncodeStructParallel-8 10000000 223 ns/op 485 B/op 10 allocs/op 294 BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 2000000 724 ns/op 96 B/op 1 allocs/op 295 BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 10000000 246 ns/op 96 B/op 1 allocs/op 296 BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 500000 3187 ns/op 2977 B/op 36 allocs/op 297 BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1106 ns/op 2977 B/op 36 allocs/op 298 BenchmarkComplexArrayDecodeStructAllTypes-8 100000 13748 ns/op 2248 B/op 121 allocs/op 299 BenchmarkComplexArrayDecodeStructAllTypesParallel-8 500000 4313 ns/op 2249 B/op 121 allocs/op 300 BenchmarkComplexArrayEncodeStructAllTypes-8 200000 10758 ns/op 7113 B/op 104 allocs/op 301 BenchmarkComplexArrayEncodeStructAllTypesParallel-8 500000 3532 ns/op 7113 B/op 104 allocs/op 302 BenchmarkComplexMapDecodeStructAllTypes-8 100000 17644 ns/op 5305 B/op 130 allocs/op 303 BenchmarkComplexMapDecodeStructAllTypesParallel-8 300000 5470 ns/op 5308 B/op 130 allocs/op 304 BenchmarkComplexMapEncodeStructAllTypes-8 200000 11155 ns/op 6971 B/op 129 allocs/op 305 BenchmarkComplexMapEncodeStructAllTypesParallel-8 500000 3768 ns/op 6971 B/op 129 allocs/op 306 BenchmarkDecodeNestedStruct-8 500000 2462 ns/op 384 B/op 14 allocs/op 307 BenchmarkDecodeNestedStructParallel-8 2000000 814 ns/op 384 B/op 14 allocs/op 308 BenchmarkEncodeNestedStruct-8 1000000 1483 ns/op 693 B/op 16 allocs/op 309 BenchmarkEncodeNestedStructParallel-8 3000000 525 ns/op 693 B/op 16 allocs/op 310 ``` 311 312 Competitor benchmarks can be found [here](https://github.com/go-playground/form/blob/master/benchmarks/benchmarks.md) 313 314 Complimentary Software 315 ---------------------- 316 317 Here is a list of software that compliments using this library post decoding. 318 319 * [Validator](https://github.com/go-playground/validator) - Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving. 320 * [mold](https://github.com/go-playground/mold) - Is a general library to help modify or set data within data structures and other objects. 321 322 Package Versioning 323 ---------- 324 I'm jumping on the vendoring bandwagon, you should vendor this package as I will not 325 be creating different version with gopkg.in like allot of my other libraries. 326 327 Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE, 328 it is so freeing not to worry about it and will help me keep pouring out bigger and better 329 things for you the community. 330 331 License 332 ------ 333 Distributed under MIT License, please see license file in code for more details.