JSON is the lingua franca of APIs and config, and struct tags are how Go maps it to types.
The encoding/json package converts between Go values and JSON:
json.Marshal(v)returns[]byte;json.MarshalIndentpretty-prints.json.Unmarshal(data, &v)decodes into a pointer.json.NewEncoder(w).Encode(v)andjson.NewDecoder(r).Decode(&v)stream to and from anio.Writer/io.Reader, such as an HTTP body.
Only exported fields are encoded; lower-case fields are silently ignored — the classic "my JSON is {}" bug. Struct tags customise the mapping:
type User struct {
ID int `json:"id"`
Email string `json:"email,omitempty"`
Password string `json:"-"`
}json:"name"sets the key;-skips the field.omitemptydrops zero values (empty string, 0, nil, empty slice or map). It doesn't treat an empty struct as empty. Newer Go versions (1.24+) addomitzerofor that.- Matching on decode is case-insensitive, and unknown keys are ignored unless you call
dec.DisallowUnknownFields().
Type mapping: objects decode into structs or map[string]any, arrays into slices, numbers into any numeric field — and into float64 when the target is any. Use a pointer field (*int) when you must distinguish "missing" from zero. time.Time encodes as RFC 3339.
For custom formats, implement MarshalJSON/UnmarshalJSON, or encoding.TextMarshaler for values that are naturally strings. json.RawMessage defers decoding of part of a document.
Example
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Order struct {
ID int `json:"id"`
Items []string `json:"items"`
Note string `json:"note,omitempty"`
token string // unexported: never encoded
}
func main() {
b, _ := json.Marshal(Order{ID: 7, Items: []string{"tea"}, token: "x"})
fmt.Println(string(b)) // {"id":7,"items":["tea"]}
var o Order
dec := json.NewDecoder(strings.NewReader(`{"id": 8, "items": ["a","b"], "note": "rush"}`))
if err := dec.Decode(&o); err != nil {
fmt.Println("decode:", err)
return
}
fmt.Printf("%+v\n", o) // {ID:8 Items:[a b] Note:rush token:}
}Remember
- Only exported fields are (un)marshalled
- Struct tags set names:
json:"name,omitempty",json:"-" - Use Decoder/Encoder for streams like HTTP bodies
Watch out for
- Passing a value instead of a pointer to json.Unmarshal — it returns an InvalidUnmarshalError.