Deep dive into Go Struct Tags

After writing Go for about 8 months now, one of the things that kept bamboozling me was the presence of struct tags. These struct tags magically serialize/deserialize data in the tags specified to and from a Go struct. But as they say, there’s no magic! This actually is a really powerful language feature of Go.

Growing as a Go developer: From dark age to advance language features

When I first wrote my first web server in Go, the struct tags were the (only? no) way to go.
A typical entity is represented like this:

type Todo struct {
ID int `json:"id"`
Content string `json:"content"`
Timestamp int64 `json:"ts"`
}

and it did get me thinking that these tags are built inside of the language’s core and can’t be customized, until I found a few third party libraries making use of it. Especially the DB connectors and ORM libraries. Here’s how the DB struct looks for the above Todo struct (for a third party library sqlx):

type Todo struct {
ID int `db:"id"`
Content string `db:"content"`
Timestamp int64 `db:"ts"`
}

which gets converted to a row in DB.

Question is, how?

Unleashing reflection in Go

If you are not familiar with reflection in programming languages, I recommend reading about it here, before proceeding.

The reflect package shipped with Go enables accessing meta data and values of arbitrary types. Covering everything it does falls out of the scope of this story and will require many! Lets stick to Struct tags for now.

Lets make use of reflect to access a type’s metadata at runtime:

Outputs:

ID id 1
Content content Chop carrots
Timestamp ts 1257894000

Lets dive into the code to understand how does it works line by line. Notice that I will be skipping imports and struct type declaration. Lets start with main() then:

todo := NewTodo(1, "Chop carrots", time.Now().Unix())

Declared a new Todo with specified values.

v := reflect.ValueOf(todo)

The ValueOf(interface{}) takes in a variable and returns a reflect.Value value, which contains the actual value contained within a variable along with additional info like ‘what type is this variable?’, ‘is it equal to Zero value of its type?’, etc.

v.Kind() == reflect.Ptr 

The reflect.Value type has Kind() defined on it that returns its kind. The return type of Kind() is reflect.Kind, which is a derived type of uint representing type of values in Go. In simpler words, reflect.Kind is a number assigned to every type in Go for the reflect library. These numbers are unique for each type and are defined on package level inside reflect. For example, reflect.Bool is a reflect.Kind that represents a value of type Boolean in reflect, reflect.Ptr represents pointer type, etc. Hence, in this line of code, we are checking if todo is a pointer to some value.

v.Kind() == reflect.Struct

Since Struct Tags can be only applied to struct kinds, panic if v’s kind is not reflect.Struct.

v = v.Elem()

In case todo is a pointer or an interface to some value, we need to get its value. This is done using Elem() present on reflect.Value. Elem() returns the value that the interface v contains or that the pointer v points to. Notice that this is applicable only if v is of reflect.Interface or reflect.Ptr. If v is nil, then Zero value is returned.

t := v.Type()

Finally get the type actual type representation of todo. The Type() on reflect.Value returns the underlying type representation. The return type here is the reflect.Type interface, which is the type representation of type it is associated to.

for i := 0; i < v.NumField(); i++

We need to iterate over every field present in the struct. The NumField() on reflect.Value returns the number of fields present on the underlying struct type.

fmt.Println(t.Field(i).Name,
t.Field(i).Tag.Get("db"),
v.Field(i).Interface())

Next, use Field(int) inside reflect.Type to access the field information in form of reflect.StructField type. The StructField type contains metadata of the field. We’re here printing the Name of the field using t.Field(i).Name . The struct tag of this field is present in a field called Tag, i.e. t.Field(i).Tag contains tag information. The type of this field reflect.StructTag and it’s a derived type of string. reflect.StructTag has Get(key string) method defined on it, which accepts the struct tag key we are searching for and returns the corresponding value. In our case, we need the tag associated with key "db" , hence we use t.Field(i).Tag.Get("db") to get it. Finally, we also would like to print the actual value of the field. To do this, we use v.Field(i) which is the reflect.Value form of a field and we get its value as interface{} type using v.Field(i).Interface() .

Summarizing learning

We have managed to break down struct tags using reflection in Go. All the serialization and deserialization libraries heavily use custom struct tags in order to transform the data whichever way they want.

Where to go from here?

  • Since working with types at runtime circles around reflection, it is highly recommended to look into the reflect package.
  • Internals of encoding/json package. Especially the unexported typeFields function.

References

--

--

--

SMTS • Web Services • Rust/Go/Py/JS • Cloud & Distributed Systems

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Spring: A Head Start 🔥— Introduction (Part 1)

Operators in Java

The path to be the Google Ninja

Create new branch with git command line

Post-Mortem Outage

Azure Virtual WAN Transit Routing and Proxy

Far off Work, Remote Employees, and Remote Desktops — It is the ideal chance for AWS WorkSpaces

Deploy over SSH from Gitlab CI pipeline

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Gaurav Saini

Gaurav Saini

SMTS • Web Services • Rust/Go/Py/JS • Cloud & Distributed Systems

More from Medium

Part 2: Grpc Proto Code Generation Using Protoc for Message and Services

Sample Go daemon process using context, wait and signal (Ctrl-C)

Concatenating strings in GO- Performance comparison between different techniques

Complete guide on Golang Control Structure