Image generated with GopherKon

Parsing Struct Tags

An excerpt from Effective Go Recipes by Miki Tebeka

The Pragmatic Programmers
3 min readAug 18, 2023

--

https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

Task

You’re building an ORM (object-relational mapper) for your company’s proprietary database. An ORM simplifies working with the database; you store and retrieve Go structs directly instead of using []any.

Your first task is to parse a user struct and return the mapping between the field names and the database columns. For example, for the following struct:

// Log is a log structure

type Log struct {
Time time.Time `db:"ts"`
Level int `db:"level"`
Text string `db:"message"`
}

You should return the mapping of:

  • Time → ts
  • Level → level
  • Text → message

Solution

You start by defining the parsing function signature:

func parseStructTags(s any) (map[string]string, error) {

First you use the reflect package to get the type of the parameter:

typ := reflect.TypeOf(s)
if typ.Kind() != reflect.Struct {
return nil, fmt.Errorf("%s is not a struct", typ)
}

Then you iterate over the struct fields and extract the db key from the field tag:

m := make(map[string]string)
for i := 0; i < typ.NumField(); i++ {
fld := typ.Field(i)
if dbName := fld.Tag.Get("db"); dbName != "" {
m[fld.Name] = dbName
}
}
return m, nil

Discussion

Since parseStructTags should accept any value, the type of its parameter is any. Most of the time, using any is a code smell and you should use concrete types or an interface.

However, in this case, you can’t use a concrete type or an interface, so you’re left with using any.

📝 Note: The term code smell was coined by Kent Beck and means a surface indication that usually corresponds to a deeper problem in the system.

Struct tags are used by many serialization packages; the built-in encoding/json, encoding/xml, and many other external packages such as yaml use them.

Struct tags allow you to add extra information about a field. They have a known format: key:”value” key:”value” … The Field type in the reflect package has methods to extract a specific key.

In some cases, the value can be more than just a name. Commonly it’s either a list of values separated by a , or a name=value. Here’s an example from a struct generated by Google’s protoc tool:

Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"`

The struct tag has two keys: protobuf and json and both have complex values.

If you decide you need complex values (like protobuf in our example), you need to design the format used in these tags since you’re basically implementing your own serialization format.

It’s best to copy what encoding/json or protobuf are doing instead of inventing your own.

We hope you enjoyed this excerpt from Effective Go Recipes by Miki Tebeka. The book is now in beta from The Pragmatic Bookshelf:

We’d love to hear what you think, so if you get the beta version, please take a moment to share comments and suggestions on the book’s page on DevTalk.

book cover featuring a cartoon frying pan tossing ingredients into the air

--

--

The Pragmatic Programmers

We create timely, practical books and learning resources on classic and cutting-edge topics to help you practice your craft and accelerate your career.