Go struct tag alignment

Over the past few months, we’ve been moving several computation heavy components of Enectiva from Ruby to Go. Our initial motivation for this move was performance — Go beats Ruby by orders of magnitude. After finishing that process, we’ve started to develop some of the new components of the Enectiva platform in Go from scratch; the most recent addition is API Librarian for collecting readings and other data points from customers.

As we’re learning Go, we’ve made mistakes and had some problems. We haven’t encountered anything revelatory or world changing, but we decided to share the lessons we’ve learned in hope it might help others.

Go has a pretty strict formatting rules enforced by the compiler. The authors intentionally restricted the style variability in order to avoid unproductive flame wars about parentheses and indentation. For the things which are not enforced by the compiler, there’s gofmt.

Gofmt is command line tool for automated formatting of source code. The target format is derived from the conventions used in the standard library. It’s considered a good practice to run gofmt on your source code before committing any shared code. The changes gofmt does are pretty simple and exactly what you’d expect: fix indentation, remove extra whitespace, align struct field definitions etc.

The latter led us to some headscratching. Structs in Go get aligned. A struct defined like this:

type Reading struct {
    MeterId int64
    Time time.Time
    Value float64
}

gets turned by gofmt into much more readable:

type Reading struct {
    MeterId int64
    Time    time.Time
    Value   float64
}

Go has another nice feature for structs. Each field can have a series of tags which are accessible through the reflection package. A common example are serialization hints for the encoding package.

type Reading struct {
    MeterId int64       `json:"meter_id"`
    Time    time.Time   `json:"time"`
    Value   float64     `json:"value"`
}

This declaration tells the JSON package to serialize Reading.MeterId field in Go into meter_id attribute in JSON (and vice versa). By using these tags, one can avoid having to write a lot of serialization/deserialization code prone to typos.

There are situations when it’s useful to have multiple declarations in a tag for a single field, e.g. how to serialize to JSON and BSON. Go allows that by separating each key-value pair by a space:

type Reading struct {
    MeterId int64       `json:"meter_id" bson:"meter_id"`
    Time    time.Time   `json:"time" bson:"time"`
    Value   float64     `json:"value" bson:"value"`
}

At this point, one might thing that running gofmt will nicely align all the BSON declarations into one column. It doesn’t. Only the beginning of the whole tag string gets aligned, not the individual key-value pairs within. That’s strange and we want nicely formatted Go code, so let me just press tab three times to get it there. That’s a mistake.

In our case, we had JSON declaration for parsing HTTP JSON payload and BSON for MongoDB. The values were the same, but the keys different. Suddenly, after the reformatting, all the readings loaded from the database were zeroed out. But any new readings when saved and loaded again were ok.

The cause turned out to be simple and described in the documentation:

By convention, tag strings are a concatenation of optionally space-separated key:“value” pairs. Each key is a non-empty string consisting of non-control characters other than space (U+0020 ' ‘), quote (U+0022 ‘"'), and colon (U+003A ‘:'). Each value is quoted using U+0022 ‘"’ characters and Go string literal syntax.

Golang: Package reflect, emphasis added

Tab causes the reflect package to stop parsing the tag string and ignore all other key-value pairs. However, it doesn’t raise any error. Most packages using struct tags have some sane defaults so a missing tag is usually not at error.

The lesson to take away from this is: if you want nicely aligned struct tags, use spaces and never tabs.

We're looking for developers to help us save energy

If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.

comments powered by Disqus