/sfv

A Golang implementation of HTTP Structured Field Values, aka RFC 8941.

Primary LanguageGoMIT LicenseMIT

sfv

Go Reference

github.com/ucarion/sfv is a Golang implementation of Structured Field Values, aka RFC 8941. You can use sfv to encode and decode data in well-formatted HTTP headers. This package is fully compliant with the standard SFV test suite.

Installation

You can install this package by running:

go get github.com/ucarion/sfv

Example

The cleverness of the Structured Field Values specification is that it retroactively makes sense of a lot of existing HTTP headers. For instance, the Content-Type header happens to be a well-formed SFV header:

Content-Type: text/html; charset=utf-8

Here's a struct you can use with sfv to read and write this sort of data:

type ContentType struct {
    MediaType string
    Charset   string `sfv:"charset"`
}

So now you can parse Content-Type headers:

var contentType ContentType
if err := sfv.Unmarshal("text/html; charset=utf-8", &contentType); err != nil {
    panic(err)
}

fmt.Println(contentType.MediaType) // Outputs: text/html
fmt.Println(contentType.Charset)   // Outputs: utf-8

Or write them out:

contentType := ContentType{MediaType: "text/html", Charset: "utf-8"}
out, err := sfv.Marshal(contentType)

fmt.Println(err) // Outputs: <nil>
fmt.Println(out) // Outputs: text/html;charset=utf-8

The online reference documentation has dozens of examples of how you can convert SFV items, lists, or dictionaries to/from their Golang equivalents.

Losslessly round-tripping SFV data

When you use sfv with custom types, as shown in the example above, you may lose some information. For instance, sfv.Unmarshal will ignore parameters that aren't specified in your struct, and will not preserve the order of parameters or dictionaries.

If you need to round-trip data exactly, you can use sfv.Marshal or sfv.Unmarshal with the sfv.Item, sfv.List, and sfv.Dictionary types. These types are treated specially by sfv, and guarantee that no data (except for things like extra whitespace) will be lost in the process of serializing/deserializing data.

For instance:

var dict sfv.Dictionary
sfv.Unmarshal("a=1,c=3,b=2", &dict)

// You can use sfv.Dictionary to iterate over keys in the order they appeared in
// the input.
fmt.Println(dict.Keys) // Outputs: [a c b]

// If dict were a map[string]int, the order of keys in this output would not be
// guaranteed.
fmt.Println(sfv.Marshal(dict)) // Outputs: a=1,c=3,b=2 <nil>