Frontend Labs
⋯

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.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36