form

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2025 License: Apache-2.0 Imports: 11 Imported by: 2

README

form CI Go Report Card Godoc Reference

Package form decodes url.Values into Go-values and encodes Go-values into url.Values.

Features

  • Allows for Custom Type registration.
  • Supports map of almost all types.
  • Supports both Numbered and Normal arrays, such as “Array[0]” and just “Array” with multiple values passed in.
  • Supports Encoding & Decoding of almost all Go types, For example, it can Decode to struct, array, map, int... and Encode to struct, array, map, int....
  • Slice honours the specified index. For example, if `"Slice[2]” - is the only Slice value passed, it will be placed in index 2, if slice is not large enough, it will be expanded.
  • Array honors the specified index. For example, if “Array[2]” - is the only Array value passed, it will be put in index 2, if the array is not large enough, a warning will be printed and the value will be ignored.
  • Creates objects only as needed; for example, if no array or map values are passed, then array and map are left as their default values in the struct.
  • Handles time.Time using RFC3339 time format by default, but can be easily changed by registering a custom type.

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

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

Installation

	go get github.com/pchchv/form

Import

	import "github.com/pchchv/form"

Usage

  • Use symbol . for separating fields/structs. (eg. structfield.field)
  • Use [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="pchchv"/>
  <input type="text" name="Age" value="3"/>
  <input type="text" name="Gender" value="Male"/>
  <input type="text" name="Address[0].Name" value="29 Any street"/>
  <input type="text" name="Address[0].Phone" value="9(999)999-9999"/>
  <input type="text" name="Address[1].Name" value="2 Some 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/pchchv/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() {
	var user User
	decoder = form.NewDecoder()
	values := parseForm() // this simulates the results of http.Request's ParseForm() function
	// must pass a pointer
	if err := decoder.Decode(&user, values); err != nil {
		log.Panic(err)
	}

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

// parseForm simulates the results of http.Request's ParseForm() function.
func parseForm() url.Values {
	return url.Values{
		"Name":                []string{"pchchv"},
		"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/pchchv/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 Encoder, it caches struct info
var encoder *form.Encoder

func main() {
	encoder = form.NewEncoder()
	user := User{
		Name:   "pchchv",
		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{})

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%pchchv"} will call the custom type function with 'User' as the type, however url.Values{"User.Name":"pchchv"} will not.

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

Ignoring Fields

It is possible to tell the form to ignore fields by using - in the tag.

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

Omitempty

It is possible to 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"`
}

Compatibility

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

// 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"}

Documentation

Overview

Package *form* Decodes url.Values into Go values and Encodes Go values into url.Values.

It has the following features:

  • Primitives types cause zero allocations.
  • Supports map of almost all types.
  • Supports both Numbered and Normal arrays. eg. "Array[0]" and just "Array" with multiple values passed.
  • 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.
  • 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.
  • 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.
  • Allows for Custom Type registration.
  • Handles time.Time using RFC3339 time format by default, but can easily be changed by registering a Custom Type, see below.
  • Handles Encoding & Decoding of almost all Go types. eg. can Decode into struct, array, map, int... and Encode a struct, array, map, int...

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 (eg. 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. (eg. `structfield.field`)
  • Use `[index or key]` for access to index of a slice/array or key for map. (eg. `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 decoding the above HTML

	package main

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

	    "github.com/pchchv/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() {
	    var user User
        decoder = form.NewDecoder()
	    values := parseForm() // this simulates the results of http.Request's ParseForm() function
	    // must pass a pointer
	    if err := decoder.Decode(&user, values); err != nil {
	        log.Panic(err)
	    }

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

	// parseForm 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"},
	    }
	}

example encoding

package main

import (
    "fmt"
    "log"

    "github.com/pchchv/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 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"}

Index

Constants

View Source
const (
	ModeImplicit Mode = iota // ModeImplicit tries to parse values for all fields that do not have an ignore '-' tag.
	ModeExplicit             // ModeExplicit parses values for field with a field tag and that tag is not the ignore '-' tag.
	// AnonymousEmbed embeds anonymous data when encoding
	// eg. type A struct { Field string }
	//     type B struct { A, Field string }
	//     encode results: url.Values{"Field":[]string{"B FieldVal", "A FieldVal"}}
	AnonymousEmbed AnonymousMode = iota

	// AnonymousSeparate does not embed anonymous data when encoding
	// eg. type A struct { Field string }
	//     type B struct { A, Field string }
	//     encode results: url.Values{"Field":[]string{"B FieldVal"}, "A.Field":[]string{"A FieldVal"}}
	AnonymousSeparate
)

Variables

This section is empty.

Functions

func ExtractType

func ExtractType(current reflect.Value) (reflect.Value, reflect.Kind)

ExtractType gets the actual underlying type of field value.

Types

type AnonymousMode

type AnonymousMode uint8

AnonymousMode specifies how data should be rolled up or separated from anonymous structs.

type DecodeCustomTypeFunc

type DecodeCustomTypeFunc func([]string) (interface{}, error)

DecodeCustomTypeFunc allows for registering/overriding types to be parsed.

type DecodeErrors

type DecodeErrors map[string]error

DecodeErrors is a map of errors encountered during form decoding

func (DecodeErrors) Error

func (d DecodeErrors) Error() string

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder is the main decode instance

func NewDecoder

func NewDecoder() *Decoder

NewDecoder creates a new decoder instance with sane defaults

func (*Decoder) Decode

func (d *Decoder) Decode(v interface{}, values url.Values) (err error)

Decode parses the given values and sets the corresponding struct and/or type values. Decode returns an InvalidDecoderError if interface passed is invalid.

func (*Decoder) RegisterCustomTypeFunc

func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...interface{})

RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types.

NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing.

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%3Djack"} will call the custom type function with `User` as the type, however url.Values{"User.Name":"jack"} will not.

func (*Decoder) RegisterTagNameFunc

func (d *Decoder) RegisterTagNameFunc(fn TagNameFunc)

RegisterTagNameFunc registers a custom tag name parser function

NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing.

ADDITIONAL: once a custom function has been registered the default, or custom set, tag name is ignored and relies 100% on the function for the name data. The return value WILL BE CACHED and so return value must be consistent.

func (*Decoder) SetMaxArraySize

func (d *Decoder) SetMaxArraySize(size uint)

SetMaxArraySize sets maximum array size that can be created. This limit is for the array indexing this library supports to avoid potential DOS or man-in-the-middle attacks using an unusually high number.

Default is 10000.

func (*Decoder) SetMode

func (d *Decoder) SetMode(mode Mode)

SetMode sets the mode the decoder should run Default is ModeImplicit

func (*Decoder) SetNamespacePrefix

func (d *Decoder) SetNamespacePrefix(namespacePrefix string)

SetNamespacePrefix sets a struct namespace prefix.

func (*Decoder) SetNamespaceSuffix

func (d *Decoder) SetNamespaceSuffix(namespaceSuffix string)

SetNamespaceSuffix sets a struct namespace suffix.

func (*Decoder) SetTagName

func (d *Decoder) SetTagName(tagName string)

SetTagName sets the given tag name to be used by the decoder.

Default is "form".

type EncodeCustomTypeFunc

type EncodeCustomTypeFunc func(x interface{}) ([]string, error)

EncodeCustomTypeFunc allows for registering/overriding types to be parsed.

type EncodeErrors

type EncodeErrors map[string]error

EncodeErrors is a map of errors encountered during form encoding.

func (EncodeErrors) Error

func (e EncodeErrors) Error() string

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder is the main encode instance.

func NewEncoder

func NewEncoder() *Encoder

NewEncoder creates a new encoder instance with sane defaults.

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) (values url.Values, err error)

Encode encodes the given values and sets the corresponding struct values.

func (*Encoder) RegisterCustomTypeFunc

func (e *Encoder) RegisterCustomTypeFunc(fn EncodeCustomTypeFunc, types ...interface{})

RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types.

NOTE: this method is not thread-safe it is intended that these all be registered prior to any parsing.

func (*Encoder) RegisterTagNameFunc

func (e *Encoder) RegisterTagNameFunc(fn TagNameFunc)

RegisterTagNameFunc registers a custom tag name parser function.

NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing.

ADDITIONAL: once a custom function has been registered the default, or custom set, tag name is ignored and relies 100% on the function for the name data. Returns value WILL BE CACHED and so return value must be consistent.

func (*Encoder) SetAnonymousMode

func (e *Encoder) SetAnonymousMode(mode AnonymousMode)

SetAnonymousMode sets the mode the encoder should run Default is AnonymousEmbed.

func (*Encoder) SetMode

func (e *Encoder) SetMode(mode Mode)

SetMode sets the mode the encoder should run.

Default is ModeImplicit.

func (*Encoder) SetNamespacePrefix

func (e *Encoder) SetNamespacePrefix(namespacePrefix string)

SetNamespacePrefix sets a struct namespace prefix.

func (*Encoder) SetNamespaceSuffix

func (e *Encoder) SetNamespaceSuffix(namespaceSuffix string)

SetNamespaceSuffix sets a struct namespace suffix.

func (*Encoder) SetTagName

func (e *Encoder) SetTagName(tagName string)

SetTagName sets the given tag name to be used by the encoder.

Default is "form"

type InvalidDecoderError

type InvalidDecoderError struct {
	Type reflect.Type
}

InvalidDecoderError describes an invalid argument passed to Decode. Argument passed to Decode must be a non-nil pointer.

func (*InvalidDecoderError) Error

func (e *InvalidDecoderError) Error() string

type InvalidEncodeError

type InvalidEncodeError struct {
	Type reflect.Type
}

InvalidEncodeError describes an invalid argument passed to Encode.

func (*InvalidEncodeError) Error

func (e *InvalidEncodeError) Error() string

type Mode

type Mode uint8

Mode specifies which mode the form decoder is to run.

type TagNameFunc

type TagNameFunc func(field reflect.StructField) string

TagNameFunc allows for adding of a custom tag name parser

Directories

Path Synopsis
examples
decoder command
encoder command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL