parsing a form

So you want to parse a form. You have your handler, and you’re getting data passed in through the request.

1
2
3
4
5
package example 

func Login(w http.ResponseWriter, r *http.Request) {
    // hi (:
}

The next step is to parse the form on the request.

1
2
3
4
5
6
func Login(w http.ResponseWriter, r *http.Request) {
    var err error 
    if err := r.ParseForm(); nil != err {
        // Serve error 
    }
}

This will populate r.Form. From here, we can use gorilla’s schema package to decode the form values onto a instantiated struct.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package example 

import (
    "github.com/gorilla/schema"
)

func Login(w http.ResponseWriter, r *http.Request) {

    var err error 
    if err := r.ParseForm(); nil != err {
        // Serve error 
    }

    // Define a struct to decode to. 
    type form struct {
        Username
        Password 
    }

    // Instantiate a form struct.
    var f form 
    
    // Instantiate a new decoder. 
    d := schema.NewDecoder()

	// Allows us to only define the values 
    // we care about in the form struct, 
    // and ignore any other values.
	d.IgnoreUnknownKeys(true)

    // Decode the response into the form struct.
	var f form
	if err = d.Decode(&f, r.Form); nil != err {
		// Serve error
	}
}

There are several things to notice here. The first is the IgnoreUnknownKeys setting. I can see where the creators of the schema package were attempting to prevent users from not explicitly handling portions of a form, but found that not knowing this setting was a severe disadvantage when writing a resusable handler across multiple forms. I wasted so much time trying to figure out how to avoid having to state all entries of all the forms it would ever encounter when I was writing a general recaptcha handler (it doesn’t need the other values in order to do its job before moving on to the child handler). All this to say that this setting is important enough that it should be at least bolded in the docs.

The second thing to notice is that I ignore the recommendation by the creators of the package to define the decoder globally. I feel that this makes the code harder to read than instantiating a decoder individually, especially if you have an extremely large file.

This may all seem familiar to you. You might be asking: why should I use this method instead of the json package with Unmarshal?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package example 

import (
    "encoding/json"
    "io/ioutil"
)

func Login(w http.ResponseWriter, r *http.Request) {
    var err error 

    // Define a struct to decode to. 
    type form struct {
        Username
        Password 
    }

    // Instantiate a form struct.
    var f form 

    var b []byte 
 	if b, err = ioutil.ReadAll(r.Body); nil != err {
		// Serve error 
	}

	if err = json.Unmarshal(b, &f); nil != err {
		// Serve error 
	}
}

The key to this question lies on the data type we are being sent. By accessing the header data in the request via r.Header.Get("Content-Type"), and determine whether we are being sent form URL encoded data, or json. The gorilla decode package is used for POSTs (or PUTs) of URL encoded data, while unmarshaling works for json. If you are unsure of which data type you’re being passed, you may add a switch statement to the beginning of the handler that checks the type before proceeding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package example 

import (
    "encoding/json"
    "io/ioutil"
    "github.com/gorilla/schema"
)

func Login(w http.ResponseWriter, r *http.Request) {
    switch strings.ToLower(r.Header.Get("Content-Type")) {
    case "application/json", "application/json; charset=UTF-8":
        // Use the json.Unmarshal().

    case "application/x-www-form-urlencoded":
        // Use gorilla's schema package.

    }
}

If you’re curious, according to MDN’s documentation, URL encoded is a bit like how it sounds: “the keys and values are encoded in key-value tuples separated by ‘&', with a ‘=’ between the key and the value”.

Now go forth and parse some forms!