While you're reading this, keep in mind that I'm available for hire! If
you've got a JavaScript project getting out of hand, or a Golang program
that's more "stop" than "go," feel free to get in touch with me. I might be
able to help you. You can find my resume here.

The Go standard library has a pretty solid package for parsing URLs and query
parameters, in the form of net/url. The way
the query parameter parser stores data though discards the order of
parameters. Most of the time this is just fine, but recently I was
implementing an API that cared a lot about the order of query parameters.
Turns out parsing query parameters isn’t too hard, and I wrote my own library
for it. You can find it at fknsrs.biz/p/urlqp
(godoc).

The approach that I took was to store a list of key/value pairs. This way I
can maintain the order of the parameters. The data structure I use to store a
key/value pair is type Pair [2]string, and the structure I use to store the
list is type Values []Pair. There are a couple of convenience functions on
the Values type - check out godoc for more details.

Here’s an example of the difference between using urlqp and net/url.

// We want to be able to process some query parameters in order. This API
// treats the order of query parameters significantly, using them as a set of
// operations.
func ExampleParse() {
// Add one, multiply by two, add three.
str := `add=1&multiply=2&add=3`
// We should get `5` as the result.
var expected int64 = 5
// Here we use the default `net/url` query parser. It parses the parameters
// into a `map[string][]string`, which is perfectly fine for most uses, but
// not ours.
m, _ := url.ParseQuery(str)
{
var i int64
// The order that we range over this map is intentionally undefined. That
// means we'll end up with one of two results:
//
// > add 1, add 3, multiply 2 = 8
// or
// > multiply 2, add 1, add 3 = 4
//
// This is bad for this particular use case.
for k, l := range m {
for _, v := range l {
n, _ := strconv.ParseInt(v, 10, 32)
switch k {
case "add":
i += n
case "multiply":
i *= n
}
}
}
// This is going to print `false`.
fmt.Printf("equal: %v\n", i == expected)
}
// This is using the `urlqp` parser. It preserves the order of the
// parameters as it just returns a list of key/value pairs.
q, _ := Parse(str)
{
var i int64
// Iterating through this list happens in a defined order, which is the
// order the parameters were specified in the string above.
for _, p := range q {
k, v := p[0], p[1]
n, _ := strconv.ParseInt(v, 10, 32)
switch k {
case "add":
i += n
case "multiply":
i *= n
}
}
// This will print `true`
fmt.Printf("equal: %v\n", i == expected)
}
// Output:
//
// equal: false
// equal: true
}

Below is the annotated source code of the parsing function.

// Parse tries to parse the given string into a set of values. If it fails, it
// will return an error.
func Parse(s string) (Values, error) {
// We don't need the leading question mark, and it's safe to get rid of it.
s = strings.TrimPrefix(s, "?")
// A small optimisation - an empty string means an empty set of parameters.
if s == "" {
return nil, nil
}
// Split the string on `&`, which is the standard query parameter delimiter.
// It really is this simple, since `&` has to be escaped if it's contained
// in any values, so we can't end up splitting "too much" or anything like
// that.
a := strings.Split(s, "&")
// Another small optimisation - allocate the `Values` slice with the exact
// size we need ahead of time, to avoid copying during `append()` calls.
r := make(Values, len(a))
// Iterate through the query parameters one at a time, perserving the order.
for i, p := range a {
// Split this query parameter into two parts, at the first `=`. In the
// case of a construct like `?a&b`, the length of `b` will be 1. It can't
// be zero, since even an empty string will split to a slice with one
// element.
b := strings.SplitN(p, "=", 2)
// Unescape the query parameter key. If this fails, we return an error.
k, err := url.QueryUnescape(b[0])
if err != nil {
return nil, err
}
// Set a default for the value. Empty seems reasonable.
v := ""
// If `b` has more than one element, that means the second one will be the
// parameter value, so we should grab it.
if len(b) > 1 {
// Now unescape the query parameter value. If this fails, return an
// error.
d, err := url.QueryUnescape(b[1])
if err != nil {
return nil, err
}
// Nothing failed, so we can keep the parameter value.
v = d
}
// Add this parameter pair to the set of values.
r[i] = Pair{k, v}
}
// Return the values and nil, signalling no error.
return r, nil
}