Go Struct Tag and reflect

Introduction

type Restaurant struct {
Name string
RestaurantId string `bson:"restaurant_id,omitempty"`
Cuisine string `bson:"cuisine,omitempty"`
Address interface{} `bson:"address,omitempty"`
Borough string `bson:"borough,omitempty"`
Grades []interface{} `bson:"grades,omitempty"`
}

In interacting with MongoDB, I found some unfamiliar syntax at the end of Struct fields. This article explores the reasons Struct Tags exist and the problems they solve.

Struct Tag

Metadata(data describing data) used to annotate Struct fields

Struct Tags are typically used at runtime for purposes such as: “format validation”, “data serialization”, “database field mapping”, etc. They can be used to store any data with no restrictions. Rather than manipulating data imperatively, the main purpose of Struct Tags is to describe data formats in a declarative manner.

PurposeExample
Data validationvalidate:"required,min=3"
Field permissionsauth:"admin_only"
Serialization formatcsv:"username"
Description for ORMgorm:"primaryKey"
API export controlexpose:"false"

Struct Tag Format

The Go standard for struct tag format is conventional but not mandatory:

key1:"value1" key2:"value2"

Common delimiters in value:

  • , → Parameter list, for example: json:"name,omitempty"
  • : → Key-value, for example: validate:"min=3"
  • | → Delimiter for multiple rules, for example: rule:"a|b|c" (custom style)

Struct Tags are just a string used to describe fields and do not include parsing logic; you need to implement it yourself using the reflect package.

reflect Package

The reflect package is used to observe and modify values at runtime

Since Go is a statically typed language, where all types are decided at compile time, we use the reflect package to read Struct Tags and determine program logic based on their contents.

package main
import (
"fmt"
"reflect"
)
func ValidateRequired(s any) error {
v := reflect.ValueOf(s)
t := reflect.TypeOf(s)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("validate")
if tag == "required" {
value := v.Field(i)
if value.IsZero() {
return fmt.Errorf("field '%s' is required", field.Name)
}
}
}
return nil
}
func main() {
type User struct {
Name string `validate:"required"`
}
u1 := User{Name: "John"}
fmt.Println(ValidateRequired(u1)) // <nil>
u2 := User{}
fmt.Println(ValidateRequired(u2)) // field 'Name' is required
}

Conclusion

So going back to the original question, it can be seen that MongoDB Driver for Go uses Struct Tags🔗 because they are necessary to map data to bson.

Further Reading