Hey guys! If you are a Go developer, you probably have used tags inside your structs. A tag is an optional string literal which you declare after a field. Here's an example:
// A struct corresponding to the TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers.
struct {
microsec uint64 "field 1"
serverIP6 uint64 "field 2"
process string "field 3"
}
These tags become attributes for all the fields in the corresponding field declaration. This means that field 1 is an extra attribute for microsec, field 2 is an extra attribute for serverIP6 and so on.
JSON tags
Let's see other example, this time with a JSON output. Let's say we have the following struct:
type User struct {
Name string
Age int
}
And we want to make this struct a JSON. Calling json.Marshal(User)
we will have the following output:
u := User{Name: "User Name", Age: 23}
data, _ := json.Marshal(u)
log.Println(string(data))
=> {"Name":"User Name","Age":23}
Now, what happens if we are sending JSON responses and we want the keys to be lowercase? We can make our struct's fields lowercase:
type User struct {
name string
age int
}
And the output would be the following:
=> {"name":"User Name","age":23}
But making these changes will make these fields only accessible to files from the same package. Fortunaly, we can use tags to change the output of our JSON. We just have to add them after the fields declarations:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
And the output would be the following:
=> {"name":"User Name","age":23}
We can also ignore some fields by removing them from the JSON output:
type User struct {
Name string `json:"first_name"`
Age int `json:"-"`
}
=> {"first_name":"User Name"}
Notice that we changed the Name key to be first_name in our JSON. We can name them whatever we want. Using tags in Go is very useful and common but, what if we want to use custom tags in our structs? How could we read them? Well, it's very simple!
Custom Tags
To write and read tags, the first thing we need is the reflect package. Tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.
import "reflect"
type Tag struct {
CustomTag string `tag1:"my tag"`
}
To read the value of our custom tag, we code the following:
// Initialize our tag
tag := Tag{CustomTag: "My Custom Tag"}
// Get the tag type (pkgName.Tag -> main.Tag)
tagType := reflect.TypeOf(tag)
// Get the CustomTag field
field := tagType.Field(0) // 0-base index
// Print the value of our tag
log.Println("Tag1 value:", field.Tag.Get("tag1"))
=> Tag1 value: my tag
We can even add multiple tags to CustomTag field and read them.
type Tag struct {
CustomTag string `tag1:"my tag, foo" tag2:"other tag"`
}
// Print the value of our tags
log.Println("Tag1 value:", field.Tag.Get("tag1"))
log.Println("Tag1 value:", field.Tag.Get("tag2"))
=> Tag1 value: my tag, foo
Tag2 value: other tag
Notice that adding multiple tags to a field is done with a white space and then the name of the n tag. You can also add multiple values to a specific tag by using a comma.
So far, we are only getting the values of our custom tags, but we are doing anything with them. For this example, we are going to use the User struct declared above, and we are going to add another field and a tag.
type User struct {
Name string `json:"first_name"`
Age int `json:"-"`
Birthday string `json:"birthday"`
}
Then, we are going to initialize our user:
u := User{Name: "User Name", Age: 23, Birthday: "Tuesday, Jun 4, 1991 9:00am (CST)"}
Notice the format of the birthday. We are going to add a parse tag to our Birthday field, setting the format of the string date, so we can parse it to a time.Time.
Birthday string `json:"birthday" parse:"Monday, Jan 2, 2006 3:04pm (MST)"`
In order to parse string dates to time.Time, we need to tell Go the format of our date. We use the following constants that are declared in the time package.
- Monday -> Long week day
- Jan -> Month
- 2 -> Day
- 2006 -> Long year
- 3 -> Hour 12 format
- 04 -> Minute with 0 if is < 10
- pm -> For pm or am hours
- MST -> Time Zone
We are going to add a Marshal function to return the data with the parsed date.
func (u *User) Marshal() ([]byte, error) {
// Getting the type of the User, we need to defer it.
t := reflect.TypeOf(*u)
// Getting the Birthday field
field, _ := t.FieldByName("Birthday")
// Getting the value of the parse tag
layout := field.Tag.Get("parse")
// Parse the string date, using the value of the parse tag
parsed, err := time.Parse(layout, u.Birthday)
// Setting the parsed date if it was parsed correctly
if err == nil {
u.Birthday = parsed.String()
}
return json.Marshal(u)
}
Finally, we call this function and log the JSON.
u := User{Name: "User Name", Age: 23, Birthday: "Tuesday, Jun 4, 1991 9:00am (CST)"}
data, _ := u.Marshal()
log.Println(string(data))
=> {"first_name":"User Name","birthday":"1991-06-04 09:00:00 -0600 CST"}
You can see how our birthday date is well formatted.
Conclusion
As you saw in this tutorial, using tags in Go is very useful and common. There are differnet tags: for JSON, XML, SQL, and so on, which make our fields have extra attributes and get different behaviours.
Also, we can have custom tags in our fields, and to read those values we need the reflect package. It's very straightforward.
And that's it. Thanks for reading and see you in the next one!