What are the use(s) for tags in Go?
A tag for a field allows you to attach meta-information to the field which can be acquired using reflection. Usually it is used to provide transformation info on how a struct field is encoded to or decoded from another format (or stored/retrieved from a database), but you can use it to store whatever meta-info you want to, either intended for another package or for your own use.
As mentioned in the documentation of reflect.StructTag
, by convention the value of a tag string is a space-separated list of key:"value"
pairs, for example:
type User struct {
Name string `json:"name" xml:"name"`
}
The key
usually denotes the package that the subsequent "value"
is for, for example json
keys are processed/used by the encoding/json
package.
If multiple information is to be passed in the "value"
, usually it is specified by separating it with a comma (','
), e.g.
Name string `json:"name,omitempty" xml:"name"`
Usually a dash value ('-'
) for the "value"
means to exclude the field from the process (e.g. in case of json
it means not to marshal or unmarshal that field).
Example of accessing your custom tags using reflection
We can use reflection (reflect
package) to access the tag values of struct fields. Basically we need to acquire the Type
of our struct, and then we can query fields e.g. with Type.Field(i int)
or Type.FieldByName(name string)
. These methods return a value of StructField
which describes / represents a struct field; and StructField.Tag
is a value of type [StructTag
] 6 which describes / represents a tag value.
Previously we talked about "convention". This convention means that if you follow it, you may use the StructTag.Get(key string)
method which parses the value of a tag and returns you the "value"
of the key
you specify. The convention is implemented / built into this Get()
method. If you don't follow the convention, Get()
will not be able to parse key:"value"
pairs and find what you're looking for. That's also not a problem, but then you need to implement your own parsing logic.
Also there is StructTag.Lookup()
(was added in Go 1.7) which is "like Get()
but distinguishes the tag not containing the given key from the tag associating an empty string with the given key".
So let's see a simple example:
type User struct {
Name string `mytag:"MyName"`
Email string `mytag:"MyEmail"`
}
u := User{"Bob", "[email protected]"}
t := reflect.TypeOf(u)
for _, fieldName := range []string{"Name", "Email"} {
field, found := t.FieldByName(fieldName)
if !found {
continue
}
fmt.Printf("\nField: User.%s\n", fieldName)
fmt.Printf("\tWhole tag value : %q\n", field.Tag)
fmt.Printf("\tValue of 'mytag': %q\n", field.Tag.Get("mytag"))
}
Output (try it on the Go Playground):
Field: User.Name
Whole tag value : "mytag:\"MyName\""
Value of 'mytag': "MyName"
Field: User.Email
Whole tag value : "mytag:\"MyEmail\""
Value of 'mytag': "MyEmail"
GopherCon 2015 had a presentation about struct tags called:
The Many Faces of Struct Tags (slide) (and a video)
Here is a list of commonly used tag keys:
-
json
- used by theencoding/json
package, detailed atjson.Marshal()
-
xml
- used by theencoding/xml
package, detailed atxml.Marshal()
-
bson
- used by gobson, detailed atbson.Marshal()
; also by the mongo-go driver, detailed at bson package doc -
protobuf
- used bygithub.com/golang/protobuf/proto
, detailed in the package doc -
yaml
- used by thegopkg.in/yaml.v2
package, detailed atyaml.Marshal()
-
db
- used by thegithub.com/jmoiron/sqlx
package; also used bygithub.com/go-gorp/gorp
package -
orm
- used by thegithub.com/astaxie/beego/orm
package, detailed at Models – Beego ORM -
gorm
- used bygorm.io/gorm
, examples can be found in their docs -
valid
- used by thegithub.com/asaskevich/govalidator
package, examples can be found in the project page -
datastore
- used byappengine/datastore
(Google App Engine platform, Datastore service), detailed at Properties -
schema
- used bygithub.com/gorilla/schema
to fill astruct
with HTML form values, detailed in the package doc -
asn
- used by theencoding/asn1
package, detailed atasn1.Marshal()
andasn1.Unmarshal()
-
csv
- used by thegithub.com/gocarina/gocsv
package -
env
- used by thegithub.com/caarlos0/env
package
Here is a really simple example of tags being used with the encoding/json
package to control how fields are interpreted during encoding and decoding:
Try live: http://play.golang.org/p/BMeR8p1cKf
package main
import (
"fmt"
"encoding/json"
)
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
MiddleName string `json:"middle_name,omitempty"`
}
func main() {
json_string := `
{
"first_name": "John",
"last_name": "Smith"
}`
person := new(Person)
json.Unmarshal([]byte(json_string), person)
fmt.Println(person)
new_json, _ := json.Marshal(person)
fmt.Printf("%s\n", new_json)
}
// *Output*
// &{John Smith }
// {"first_name":"John","last_name":"Smith"}
The json package can look at the tags for the field and be told how to map json <=> struct field, and also extra options like whether it should ignore empty fields when serializing back to json.
Basically, any package can use reflection on the fields to look at tag values and act on those values. There is a little more info about them in the reflect package
http://golang.org/pkg/reflect/#StructTag :
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.