23. encoding/json and reflection, briefly
23.1 Marshal and Unmarshal
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
data, err := json.Marshal(User{ID: 1, Name: "Alice"})
if err != nil {
log.Fatal(err)
}
// {"id":1,"name":"Alice"} -- Email omitted because omitempty + zero value
var u User
if err := json.Unmarshal(data, &u); err != nil { // pointer required, Unmarshal writes into it
log.Fatal(err)
}
23.2 Streaming encode/decode for large payloads
if err := json.NewEncoder(w).Encode(response); err != nil { // writes directly to the ResponseWriter
return err
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil { // reads directly from the request body
return err
}
Streaming avoids materializing the whole JSON document as a []byte
first — meaningful for large request/response bodies.
23.3 Why Marshal/Unmarshal need reflection
json.Marshal doesn't know your struct's shape at compile time — it
walks the struct's fields at runtime via the reflect package, reading
tags and field types. This is also why JSON (un)marshaling is
comparatively slow versus a hand-written encoder, and why hot paths in
performance-sensitive services sometimes hand-roll marshaling or use
code-generation tools (easyjson, ffjson) instead.
23.4 A two-line taste of reflect
v := reflect.ValueOf(someStruct)
t := reflect.TypeOf(someStruct)
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Name, "=", v.Field(i).Interface())
}
Reflection is powerful and dangerous: it defeats compile-time type checking, and code that leans on it heavily is harder to read and often slower. Chapter 32 covers when it's actually the right tool.