A struct tag attaches metadata to a Go struct field. It is a backtick-quoted string after the field’s type. The string is freeform, but the usual convention is whitespace-separated entries such as `json:"animal_name"` and `xml:"AnimalName"`. A tag does nothing by itself: code that reads the struct decides which entries to use. section 3.1.4 section 13.2.1
Listing 3.4 Formatting data as JSON
package main
import (
"fmt"
"encoding/json"
)
type Animal struct { #1
Name string `json:"animal_name"` #2
ScientificName string `json:"scientific_name"` #2
Weight float32 `json:"animal_average_weight"` #2
}
func main() {
a := Animal{
Name: "cat",
ScientificName: "Felis catus",
Weight: 10.5,
} #3
output, err := json.Marshal(a)
if err != nil {
panic("couldn't encode json")
}
fmt.Println(string(output)) #4
}
The exported Go fields have `json` tags naming the output fields. `json.Marshal` uses those names; without the tags, it would use the Go field names. section 3.1.5
The field names are capitalized so `encoding/json` can access them. The tags let the JSON names follow a different convention without changing the Go names. A struct can also carry tags for more than one processor: a JSON encoder selects its `json` entry and ignores an `xml` entry. A JSON tag can include options after the name, as in `json:"lastName,omitempty"`. section 3.1.5 section 13.2.1
Tags are not limited to encoding. A `help` tag can supply an error message, and a `validate` tag can hold a regular expression for application-written checks. Neither behavior happens automatically: the relevant code must retrieve and interpret the value. Reflection provides access to a field’s tags at runtime, although its runtime cost and added complexity make it a tool to use deliberately. section 3.1.4 section 13.2.2
Listing 13.11 The Processes struct with annotations
type Processes struct {
Total int `ini:"total"`
Running int `ini:"running"`
Sleeping int `ini:"sleeping"`
Threads int `ini:"threads"`
Load float32 `ini:"load"`
}
A custom `ini` tag maps each struct field to a key in a simple name-value file. Defining that mapping is only the first step; Go does not automatically parse the file from these annotations. section 13.2.3
Listing 13.14 The Marshal function
func fieldName(field reflect.StructField) string { #1
if t := field.Tag.Get("ini"); t != "" { #2
return t #2
}
return field.Name #3
}
func Marshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
val := reflect.Indirect(reflect.ValueOf(v)) #4
if val.Kind() != reflect.Struct { #5
return []byte{}, errors.New("unmarshal can only take structs")
}
t := val.Type()
for i := 0; i < t.NumField(); i++ { #6
f := t.Field(i)
name := fieldName(f) #7
raw := val.Field(i).Interface()
fmt.Fprintf(&b, "%s=%v\n", name, raw) #8
}
return b.Bytes(), nil #9
}
This encoder uses reflection to visit fields. Its `fieldName` helper reads `field.Tag.Get("ini")`, falling back to the Go field name when the tag is empty, before writing each `name=value` line. section 13.2.3
A matching decoder scans input lines, matches their keys against the same field-name rule, and converts values before setting fields. This is the practical boundary of a custom tag: choose a format, then implement the behavior that gives it meaning. section 13.2.3


