Struct Tags
A struct tag is a string on a field that libraries read by reflection to map it to a JSON key, a database column or a rule.
Name string `json:"name,omitempty" db:"user_name"`A struct tag is a short string attached to a field. The compiler ignores what it says. Libraries read it while the program runs to decide what the field is called outside your program: the key in JSON, the column in a database table, the rule a validator should apply.
How a tag is written
The tag goes after the field's type, written as a string in backticks. Backticks are used because the tag itself contains double quotes. The language allows any string there. Every library expects the same convention: a space-separated list of key:"value" pairs, as in `json:"user_name" db:"user_name"`. Each library looks up its own key and ignores the rest, so one field can describe itself to several libraries at once.
How a tag is read
Libraries read tags by reflection, which means code inspecting types and values while the program runs rather than at compile time. reflect.TypeOf(User{}) gives the type, FieldByName gives one field, and that field's Tag has two useful calls. Get("json") returns the value for that key, or an empty string if it is absent. Lookup("json") also returns a boolean saying whether the key was there, which separates absent from present-but-empty.
What trips people up
A tag is just a string, so a mistake in one is not a compile error. Straight quotes instead of backticks, a comma between pairs, a space after the colon, a misspelled key: each leaves the library seeing no tag and falling back to the field name. Run go vet, which checks struct tags for exactly these mistakes. A tag on a lowercase field is wasted too, because encoders skip unexported fields entirely.
Example
package main
import (
"fmt"
"reflect"
)
// One field can carry a key for each library that reads it.
type User struct {
Name string `json:"name" db:"user_name"`
Note string `json:"-"`
}
func main() {
t := reflect.TypeOf(User{})
f, ok := t.FieldByName("Name")
if !ok {
fmt.Println("no field called Name")
return
}
fmt.Println(f.Tag.Get("json"))
fmt.Println(f.Tag.Get("db"))
// An absent key gives an empty string from Get.
fmt.Printf("%q\n", f.Tag.Get("missing"))
// Lookup also says whether the key was present at all.
v, present := f.Tag.Lookup("missing")
fmt.Printf("%q %t\n", v, present)
}
// Output:
// name
// user_name
// ""
// "" falseWhen to use it
- Expose lowercase JSON keys while keeping exported Go field names.
- Keep a secret or internal field out of every encoded response.
- Map a struct field to a differently named database column for a scanning library.
- Attach validation rules next to the field they apply to.
More examples
Tags that change JSON output
This is the tag most people meet first. The name part renames the key, so Go keeps capitalised field names while the API stays lowercase. Plan is empty and carries omitempty, so it disappears. Token is tagged "-", so it never leaves the process, which is the usual way to keep a secret out of a response body.
package main
import (
"encoding/json"
"fmt"
)
type Account struct {
ID int `json:"id"`
Email string `json:"email"`
Plan string `json:"plan,omitempty"`
Token string `json:"-"`
}
func main() {
a := Account{ID: 3, Email: "[email protected]", Token: "secret"}
b, err := json.Marshal(a)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
// Output:
// {"id":3,"email":"[email protected]"}Three tags, two of them broken
Field A has a space after the colon and field B has a comma where the colon belongs. Both compile, and both give an empty value when a library asks for the json key, so the library falls back to the Go field name and your JSON keys come out capitalised. There is no error to search for, which is why go vet is worth running in your build.
package main
import (
"fmt"
"reflect"
)
type Broken struct {
A string `json: "a"`
B string `json,"b"`
C string `json:"c"`
}
func main() {
t := reflect.TypeOf(Broken{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s %q\n", f.Name, f.Tag.Get("json"))
}
}
// Output:
// A ""
// B ""
// C "c"One struct describing itself to three libraries
Each library reads only its own key, so one declaration can serve the HTTP layer, the database layer and the validator. That keeps the three names for one piece of data in one place. The cost is that a tag grows long, and nothing checks that the db key matches a real column until the query runs.
package main
import (
"fmt"
"reflect"
)
// json renames the API key, db names the column, validate carries a rule.
type Signup struct {
Email string `json:"email" db:"email_address" validate:"required,email"`
}
func main() {
f, ok := reflect.TypeOf(Signup{}).FieldByName("Email")
if !ok {
fmt.Println("no field called Email")
return
}
for _, key := range []string{"json", "db", "validate"} {
fmt.Printf("%s=%s\n", key, f.Tag.Get(key))
}
}
// Output:
// json=email
// db=email_address
// validate=required,email
Discussion