A Form Encoding & Decoding Package for Go

form

A Form Encoding & Decoding Package for Go, written by Alvaro J. Genial.

Build Status GoDoc

Synopsis

This library is designed to allow seamless, high-fidelity encoding and decoding of arbitrary data in application/x-www-form-urlencoded format and as url.Values. It is intended to be useful primarily in dealing with web forms and URI query strings, both of which natively employ said format.

Unsurprisingly, form is modeled after other Go encoding packages, in particular encoding/json, and follows the same conventions (see below for more.) It aims to automatically handle any kind of concrete Go data value (i.e., not functions, channels, etc.) while providing mechanisms for custom behavior.

Status

The implementation is in usable shape and is fairly well tested with its accompanying test suite. The API is unlikely to change much, but still may. Lastly, the code has not yet undergone a security review to ensure it is free of vulnerabilities. Please file an issue or send a pull request for fixes & improvements.

Dependencies

The only requirement is Go 1.2 or later.

Usage

import "github.com/ajg/form"
// or: "gopkg.in/ajg/form.v1"

Given a type like the following...

type User struct {
	Name         string            `form:"name"`
	Email        string            `form:"email"`
	Joined       time.Time         `form:"joined,omitempty"`
	Posts        []int             `form:"posts"`
	Preferences  map[string]string `form:"prefs"`
	Avatar       []byte            `form:"avatar"`
	PasswordHash int64             `form:"-"`
}

...it is easy to encode data of that type...

func PostUser(url string, u User) error {
	var c http.Client
	_, err := c.PostForm(url, form.EncodeToValues(u))
	return err
}

...as well as decode it...

func Handler(w http.ResponseWriter, r *http.Request) {
	var u User

	d := form.NewDecoder(r.Body)
	if err := d.Decode(&u); err != nil {
		http.Error(w, "Form could not be decoded", http.StatusBadRequest)
		return
	}

	fmt.Fprintf(w, "Decoded: %#v", u)
}

...without having to do any grunt work.

Field Tags

Like other encoding packages, form supports the following options for fields:

  • `form:"-"`: Causes the field to be ignored during encoding and decoding.
  • `form:"<name>"`: Overrides the field's name; useful especially when dealing with external identifiers in camelCase, as are commonly found on the web.
  • `form:",omitempty"`: Elides the field during encoding if it is empty (typically meaning equal to the type's zero value.)
  • `form:"<name>,omitempty"`: The way to combine the two options above.

Values

Simple Values

Values of the following types are all considered simple:

  • bool
  • int, int8, int16, int32, int64, rune
  • uint, uint8, uint16, uint32, uint64, byte
  • float32, float64
  • complex64, complex128
  • string
  • []byte (see note)
  • time.Time
  • url.URL
  • An alias of any of the above
  • A pointer to any of the above

Composite Values

A composite value is one that can contain other values. Values of the following kinds...

  • Maps
  • Slices; except []byte (see note)
  • Structs; except time.Time and url.URL
  • Arrays
  • An alias of any of the above
  • A pointer to any of the above

...are considered composites in general, unless they implement custom marshaling/unmarshaling. Composite values are encoded as a flat mapping of paths to values, where the paths are constructed by joining the parent and child paths with a period (.).

(Note: a byte slice is treated as a string by default because it's more efficient, but can also be decoded as a slice—i.e., with indexes.)

Untyped Values

While encouraged, it is not necessary to define a type (e.g. a struct) in order to use form, since it is able to encode and decode untyped data generically using the following rules:

  • Simple values will be treated as a string.
  • Composite values will be treated as a map[string]interface{}, itself able to contain nested values (both scalar and compound) ad infinitum.
  • However, if there is a value (of any supported type) already present in a map for a given key, then it will be used when possible, rather than being replaced with a generic value as specified above; this makes it possible to handle partially typed, dynamic or schema-less values.

Zero Values

By default, and without custom marshaling, zero values (also known as empty/default values) are encoded as the empty string. To disable this behavior, meaning to keep zero values in their literal form (e.g. 0 for integral types), Encoder offers a KeepZeros setter method, which will do just that when set to true.

Unsupported Values

Values of the following kinds aren't supported and, if present, must be ignored.

  • Channel
  • Function
  • Unsafe pointer
  • An alias of any of the above
  • A pointer to any of the above

Custom Marshaling

There is a default (generally lossless) marshaling & unmarshaling scheme for any concrete data value in Go, which is good enough in most cases. However, it is possible to override it and use a custom scheme. For instance, a "binary" field could be marshaled more efficiently using base64 to prevent it from being percent-escaped during serialization to application/x-www-form-urlencoded format.

Because form provides support for encoding.TextMarshaler and encoding.TextUnmarshaler it is easy to do that; for instance, like this:

import "encoding"

type Binary []byte

var (
	_ encoding.TextMarshaler   = &Binary{}
	_ encoding.TextUnmarshaler = &Binary{}
)

func (b Binary) MarshalText() ([]byte, error) {
	return []byte(base64.URLEncoding.EncodeToString([]byte(b))), nil
}

func (b *Binary) UnmarshalText(text []byte) error {
	bs, err := base64.URLEncoding.DecodeString(string(text))
	if err == nil {
		*b = Binary(bs)
	}
	return err
}

Now any value with type Binary will automatically be encoded using the URL variant of base64. It is left as an exercise to the reader to improve upon this scheme by eliminating the need for padding (which, besides being superfluous, uses =, a character that will end up percent-escaped.)

Keys

In theory any value can be a key as long as it has a string representation. However, by default, periods have special meaning to form, and thus, under the hood (i.e. in encoded form) they are transparently escaped using a preceding backslash (\). Backslashes within keys, themselves, are also escaped in this manner (e.g. as \\) in order to permit representing \. itself (as \\\.).

(Note: it is normally unnecessary to deal with this issue unless keys are being constructed manually—e.g. literally embedded in HTML or in a URI.)

The default delimiter and escape characters used for encoding and decoding composite keys can be changed using the DelimitWith and EscapeWith setter methods of Encoder and Decoder, respectively. For example...

package main

import (
	"os"

	"github.com/ajg/form"
)

func main() {
	type B struct {
		Qux string `form:"qux"`
	}
	type A struct {
		FooBar B `form:"foo.bar"`
	}
	a := A{FooBar: B{"XYZ"}}
	os.Stdout.WriteString("Default: ")
	form.NewEncoder(os.Stdout).Encode(a)
	os.Stdout.WriteString("\nCustom:  ")
	form.NewEncoder(os.Stdout).DelimitWith('/').Encode(a)
	os.Stdout.WriteString("\n")
}

...will produce...

Default: foo%5C.bar.qux=XYZ
Custom:  foo.bar%2Fqux=XYZ

(%5C and %2F represent \ and /, respectively.)

Limitations

  • Circular (self-referential) values are untested.

Future Work

The following items would be nice to have in the future—though they are not being worked on yet:

  • An option to treat all values as if they had been tagged with omitempty.
  • An option to automatically treat all field names in camelCase or underscore_case.
  • Built-in support for the types in math/big.
  • Built-in support for the types in image/color.
  • Improve encoding/decoding by reading/writing directly from/to the io.Reader/io.Writer when possible, rather than going through an intermediate representation (i.e. node) which requires more memory.

(Feel free to implement any of these and then send a pull request.)

Related Work

License

This library is distributed under a BSD-style LICENSE.

Owner
Alvaro J. Genial
Alvaro J. Genial
Comments
  • Intentional special casing of periods in form keys limits real-world usage

    Intentional special casing of periods in form keys limits real-world usage

    Due to the intentional special casing of periods within form, any API which uses periods in a form field cannot be interacted with without some hacks. For example, the fastly API utilizes periods in their form fields: https://docs.fastly.com/api/config#settings_9740ff4ac0c1777f455274c4850ece23

    The go-fastly library attempted to work around this by manually ripping out the generated escapes, but that leads to other things being unintentionally ripped out, as noted in this case: https://github.com/sethvargo/go-fastly/issues/11

    Is there any workaround for interacting with forms which have periods in their form keys?

  • keys with periods in them

    keys with periods in them

    A particular third-party API I wish to contact used periods in the key names. I have the keys in my structs labelled as such:

    type whatever struct { CleanupReturnCleaned bool form:"cleanup.returnCleaned" }

    but when I run form.EncodeToString() I get: cleanup%5C.returnCleaned=true

    I've read the documentation in the Readme and have tried escaping the period with a back slash but that doesnt seem to have the desired effect. Am I misreading the readme, or is this not possible using ajg/form?

    Cheers!

  • Behavior of isEmptyValue

    Behavior of isEmptyValue

    The implementation of isEmptyValue makes encoding certain structs a bit non-intuitive. Similar to the discussion here, I am trying to encode a 0 in a request.

    I would expect the following code to result in "num=0&name=test" but instead I end up with "num=&name=test" even though my value does "exist".

    package main
    
    import (
        "bytes"
        "github.com/ajg/form"
        "log"
    )
    
    type Thing struct {
        String  string `form:"name,omitempty"`
        Integer *uint  `form:"num,omitempty"`
    }
    
    func main() {
    
        a_num := uint(0)
        u := Thing{"test", &a_num}
        buf := new(bytes.Buffer)
        if err := form.NewEncoder(buf).DelimitWith('|').Encode(u); err != nil {
            log.Printf("[ERR] %s", err)
        }
        body := buf.String()
        log.Printf("[DEBUG] %s", body)
    
    }
    
  • Possibility of not indicate the index in the form html

    Possibility of not indicate the index in the form html

    Hi, I have this: type Film struct { Genres []string Topics []string }

    I am obliged to indicate the index of slice in the input form:

    Is possible NOT indicate the index and only leave "Topics"? With gorilla/schema it is possible, but gorilla/schema, in general, is much more limited...

    Thanks!

    Best, Emilio

  • Feature Request: File uploads

    Feature Request: File uploads

    It would be nice to have the ability to encode files in the form data.

    For example, we could have a special struct tag that indicates this field represents a file path.

  • Proposal: Support for Reset

    Proposal: Support for Reset

    It would be useful for my use case if both the encoder and decoder structs exposed a Reset method which allowed changing the underlying io.Writer or io.Reader without having to create new instances. This makes it possible to use sync.Pool for example to manage a set of instances. The implementation would be trivial given that they don't have state other than the writer or reader. Would you consider a PR that added these? I'm thinking something like:

    in decode.go:

    func (d *decoder) Reset(r io.Reader) {
        d.r = r
    }
    

    in encode.go

    func (e *encoder) Reset(w io.Writer) {
        e.w = w
    }
    
  • Proposal: Produce efficient errors instead raw panic message

    Proposal: Produce efficient errors instead raw panic message

    I always like to check my form fields to see if their values are convertible to needed types and if not then inform the user about invalid fields. form produces *errors.errorString and this is not useful when generating error responses for this purpose.

    What about defining a new error type such as below to give more control over them

    e.g.

    type Error []Field
    
    func (e Error) Error() string {
      return "todo"
    }
    
    type Field struct {
      Name string // tag name e.g. time from form:"time"
      Message string // original error message 
      Type reflect.Type // point out the expected type so we can generate even more graceful error messages by checking this e.g. "time must be a UTC date"
      SliceIndex int // point out the index if an invalid Type placed in a slice except []byte
    }
    

    A dummy usage:

    type Post struct {
      Time time.Time `form:"time"`
    }
    
    var p Post
    err := form.DecodeValues(&p, url.Values{"time", []string{"AnInvalidDate"}})
    if (err.(form.Error))[0].Type == form.TimeType {
     // so I can response a nice error message to user like:
     // HTTP 400
     // {"message": "Invalid Form Data", fields: {"time": "must be UTC formatted"}}
    }
    

    I'd like to implement this if you'll consider accepting the PR

  • two features

    two features

    EncodeToString, remain 'null' value:

    {“test1”: 0, “test2”: false} EncodeToString
    from
    test1=&test2=
    change to
    test1=0&test2=false
    

    fieldInfo: using 'json' tag as second choice: using 'form' as default tag, using 'json' tag when there's no 'form' tag.

  • Add flags to ignore case and ignore unknown keys

    Add flags to ignore case and ignore unknown keys

    This pull request adds two methods to the decoder{} struct: IgnoreUnknownKeys(bool) and IgnoreCase(true).

    The idea of IgnoreUnknownKeys is similar to the method with the same name in gorilla/schema: https://github.com/gorilla/schema/blob/master/decoder.go#L53 Also, setting it to true makes this lib more similar to how encoding/json handle unknown keys.

    IgnoreCase will allow matching struct fields with case insensitive names. This is only used as a fallback, if an exact match exists in the struct it will be given priority over a case insensitive match.

    To allow using these flags with DecodeValues and DecodeString I decided to add them as methods to the decoder struct, the reader is ignored if they are called. The still also exist as package functions and so this change shouldn't break anyone using this library.

    Please let me know if there's anything you'd like me to change on this PR.

  • Can decode/encode anonymous fields

    Can decode/encode anonymous fields

    Hi! In the app that I created found a problem. I use a lot of this library, it allow so much fields but not anonymous fields. For example, i have in my project a two structs:

    type Length struct {
        Title        map[string]string `json:"title"`
        Release  map[string]string `json:"release,omitempty"`
        Genres   Genre                  `json:"genres"`
        Topics    []string                 `json:"genres"`
    }
    
    type FilmBasedEpisodes struct {
        Length
        Channel  Channel
        Seasons  []Season
    }
    

    But when I want do this:

    ....
    r.ParseMultipartForm(maxMemory)
    dataFormHTML := r.Form
    film := FilmBasedEpisodes{}
    err := form.DecodeValues(&film, dataFormHTML)
    ....
    

    The content of dataFormHTML is, for example, like this:

    "Title.US":"Six Feet Under", "Title.ES": "A dos metros bajo tierra", "Channel.Original":"HBO"
    

    The error from ajg.form in go compiler is : Title doesn't exist in struct main.FilmBasedEpisodes

    I understand that the library not is compatible with anonymous fields, right? If I wrong, then, how I do it?

    Best, Emilio

  • Fix empty struct checks after change in Go 1.16

    Fix empty struct checks after change in Go 1.16

    The previous verification for empty structs was invalid and only worked by chance for structs with no fields. After the change in Go 1.16 described in https://github.com/golang/go/issues/43993 this verification stopped working.

    This PR changes how empty structs are handled and also adds new test cases to ensure that this behavior is now consistent.

  • Tests are failing on Go 1.17:

    Tests are failing on Go 1.17: "invalid semicolon separator in query"

    It looks like the tests are failing on Go 1.17 and up around parsing semicolons.

    There are a couple ways to resolve - one of them is using the Go handler: https://pkg.go.dev/net/http#AllowQuerySemicolons

    Other suggestions: https://golangshowcase.com/question/invalid-semicolon-separator-in-query-after-go-1-17

    ➜  form git:(master) ✗ go test ./...
    --- FAIL: TestDecodeString (0.00s)
        decode_test.go:17: DecodeString(";C=42%2B6.6i;A.0=x;M.Bar=8;F=6.6;A.1=y;R=8734;A.2=z;Zs.0.Qp=33_44;B=true;M.Foo=7;T=2013-10-01T07:05:34.000000088Z;E.Bytes1=%00%01%02;Bytes2=%03%04%05;Zs.0.Q=11_22;Zs.0.Z=2006-12-01;M.Qux=9;life=42;S=Hello,+there.;P\\.D\\\\Q\\.B.A=P/D;P\\.D\\\\Q\\.B.B=Q-B;U=http%3A%2F%2Fexample.org%2Ffoo%23bar;"): invalid semicolon separator in query
        decode_test.go:17: DecodeString(";C=42%2B6.6i;A.0=x;M.Bar=8;F=6.6;A.1=y;R=8734;A.2=z;Zs.0.Qp=33_44;B=true;M.Foo=7;T=2013-10-01T07:05:34.000000088Z;E.Bytes1=%00%01%02;Bytes2=%03%04%05;Zs.0.Q=11_22;Zs.0.Z=2006-12-01;M.Qux=9;life=42;S=Hello,+there.;P\\.D\\\\Q\\.B.A=P/D;P\\.D\\\\Q\\.B.B=Q-B;U=http%3A%2F%2Fexample.org%2Ffoo%23bar;"): invalid semicolon separator in query
        decode_test.go:17: DecodeString(";C=42%2B6.6i;A.0=x;M.Bar=8;F=6.6;A.1=y;R=8734;A.2=z;Zs.0.Qp=33_44;B=true;M.Foo=7;T=2013-10-01T07:05:34.000000088Z;E.Bytes1=%00%01%02;Bytes2=%03%04%05;Zs.0.Q=11_22;Zs.0.Z=2006-12-01;M.Qux=9;life=42;S=Hello,+there.;P\\.D\\\\Q\\.B.A=P/D;P\\.D\\\\Q\\.B.B=Q-B;U=http%3A%2F%2Fexample.org%2Ffoo%23bar;"): invalid semicolon separator in query
    --- FAIL: TestDecodeValues (0.00s)
    panic: invalid semicolon separator in query [recovered]
            panic: invalid semicolon separator in query
    FAIL
    
  • Serialize composite []string with empty square brackets

    Serialize composite []string with empty square brackets

    👋🏻 I have an API that expects data to be sent like so:

    services[]=A&services[]=B
    

    How can I achieve this?

    Currently the default behaviour appears to number each element within the slice.

    I see there's a DelimitWith method but that doesn't quite achieve what I need (e.g. DelimitWith('|')):

    services|0=A&services|1=B
    

    Is there a custom unmarshal method I can define that might help encode the data how I need it?

    Thanks!

  • Remove test cases that are no longer valid with go 1.17

    Remove test cases that are no longer valid with go 1.17

    As of go 1.17, the net/http and net/url packages no longer accept ';' as a valid separator. See https://golang.org/doc/go1.17#semicolons for more information.

    In order to build this package with go 1.17 and keep it in Ubuntu, the test cases that use ';' as a separator should be removed.

  • Adding Power support(ppc64le) with continuous integration/testing so that project stays architecture independent.

    Adding Power support(ppc64le) with continuous integration/testing so that project stays architecture independent.

    Please review and merge the changes ,This is part of the Ubuntu distribution for ppc64le. This helps us simplify testing later when distributions are re-building and re-releasing. For more info tag @gerrith3.

  • Fails to decode struct if field is missing

    Fails to decode struct if field is missing

    The library fails to decode a struct if field is missing.

    E.g. I do have a struct

    type TokenExchange struct {
      Type string `form:"grant_type"`
      Code string `form:"code"`
    }
    

    The input string to parse

    grant_type=authorization_code&code=xxx&client_id=xxx
    

    The library fails to decode string with an error

    client_id doesn't exist in main.TokenExchange
    

    I would expect that library skips client_id.

Related tags
A lightweight go library for parsing form data or json from an http.Request.

Forms Forms is a lightweight, but incredibly useful go library for parsing form data from an http.Request. It supports multipart forms, url-encoded fo

Dec 16, 2022
Go module for encoding structs into URL query parameters

qs Package sonh/qs encodes structs into url.Values. Installation go get github.com/sonh/qs Usage import ( "github.com/sonh/qs" ) Package qs export

Jan 7, 2023
go-eexcel implements encoding and decoding of XLSX like encoding/json

go-eexcel go-eexcel implements encoding and decoding of XLSX like encoding/json Usage func ExampleMarshal() { type st struct { Name string `eexce

Dec 9, 2021
Mantil-template-form-to-dynamodb - Receive form data and write it to a DynamoDB table
Mantil-template-form-to-dynamodb - Receive form data and write it to a DynamoDB table

This template is an example of serverless integration between Google Forms and DynamoDB

Jan 17, 2022
Go package for decoding and encoding TARGA image format

tga tga is a Go package for decoding and encoding TARGA image format. It supports RLE and raw TARGA images with 8/15/16/24/32 bits per pixel, monochro

Sep 26, 2022
Go package containing implementations of efficient encoding, decoding, and validation APIs.

encoding Go package containing implementations of encoders and decoders for various data formats. Motivation At Segment, we do a lot of marshaling and

Dec 25, 2022
MIME mail encoding and decoding package for Go

enmime enmime is a MIME encoding and decoding library for Go, focused on generating and parsing MIME encoded emails. It is being developed in tandem w

Nov 30, 2022
Package json implements encoding and decoding of JSON as defined in RFC 7159

Package json implements encoding and decoding of JSON as defined in RFC 7159. The mapping between JSON and Go values is described in the documentation for the Marshal and Unmarshal functions

Jun 26, 2022
A simple, semantic and developer-friendly golang package for encoding&decoding and encryption&decryption

A simple, semantic and developer-friendly golang package for encoding&decoding and encryption&decryption

Jan 4, 2023
encLib is a simple golang package for quickly encoding and decoding string data in hex

encLib is a simple golang package for quickly encoding and decoding string data in hex

Nov 1, 2021
? ID3 decoding and encoding library for Go

id3v2 Supported ID3 versions: 2.3, 2.4 Installation go get -u github.com/bogem/id3v2 Usage example package main import ( "fmt" "log" "github.com

Dec 31, 2022
Encoding and decoding GeoJSON <-> Go

go.geojson Go.geojson is a package for encoding and decoding GeoJSON into Go structs. Supports both the json.Marshaler and json.Unmarshaler interfaces

Jan 2, 2023
Encoding and decoding for fixed-width formatted data

fixedwidth Package fixedwidth provides encoding and decoding for fixed-width formatted Data. go get github.com/ianlopshire/go-fixedwidth Usage Struct

Dec 16, 2022
Go structure annotations that supports encoding and decoding; similar to C-style bitfields. Supports bitfield packing, self-describing layout parameters, and alignment.
Go structure annotations that supports encoding and decoding; similar to C-style bitfields. Supports bitfield packing, self-describing layout parameters, and alignment.

STRUCTure EXtensions structex provides annotation rules that extend Go structures for implementation of encoding and decoding of byte backed data fram

Oct 13, 2022
A codec for Go structs with support for chainable encoding/decoding hooks.

structool A codec for Go structs with support for chainable encoding/decoding hooks. Features Provide a uniform codec by combining mapstructure and st

Jan 15, 2022
json encoding and decoding

jx Package jx implements encoding and decoding of json [RFC 7159]. Lightweight fork of jsoniter. go get github.com/go-faster/jx Usage and examples Roa

Dec 27, 2022
Some Golang types based on builtin. Implements interfaces Value / Scan and MarshalJSON / UnmarshalJSON for simple working with database NULL-values and Base64 encoding / decoding.

gotypes Some simple types based on builtin Golang types that implement interfaces for working with DB (Scan / Value) and JSON (Marshal / Unmarshal). N

Feb 12, 2022
GED - Global-purpose Encoding / Decoding library

GED - Global-purpose Encoding / Decoding library This library lets you use common encoding/decoding schemes and allows you to define custom ones. Use

Nov 28, 2021
Custom generic HTTP handler providing automatic JSON decoding/encoding of HTTP request/response to your concrete types

gap Custom generic HTTP handler providing automatic JSON decoding/encoding of HTTP request/response to your concrete types. gap.Wrap allows to use the

Aug 28, 2022
a package for decode form's values into struct in Go

formam A Go package to decode HTTP form and query parameters. The only requirement is Go 1.10 or later. Features Infinite nesting for maps, structs an

Nov 25, 2022