Day 036 — encoding/json: Marshal & Unmarshal¶
Month 2 · Week 2 · ⬅ Day 035 · Day 037 ➡ · Journal index
🎯 Learning Objective¶
Convert Go values to and from JSON with json.Marshal/Unmarshal, understand the reflection-based encoder's rules (exported fields, default type mappings), and stream JSON with Encoder/Decoder.
📚 Topics¶
json.Marshal/json.MarshalIndent/json.Unmarshal- Exported-fields-only rule · default Go↔JSON type mapping
- Decoding into
map[string]interface{}· thefloat64number trap json.NewEncoder/NewDecoderfor streams
📖 Reading / Sources¶
-
encoding/jsonpackage docs - The Go Blog — JSON and Go
- Learning Go ch.11 (encoding/json)
📝 Notes¶
json.Marshal(v)walksvwith reflection and returns[]byte. Only exported (capitalized) struct fields are encoded; unexported fields are invisible to the encoder → [[json-exported-only]].json.Unmarshal(data, &v)decodes into the valuevpoints to — you must pass a pointer, or the decoded data has nowhere to go.- Default type mapping: JSON object→
struct/map, array→slice, string→string,true/false→bool, number→float64(intointerface{}). The number→float64trap loses int precision past 2^53; use aDecoderwithUseNumber()or decode into a typed field when precision matters → [[json-number-trap]]. - Unknown JSON keys are silently ignored; missing keys leave the Go zero value. Decoding is lenient by default — call
dec.DisallowUnknownFields()to make extra keys an error. MarshalIndent(v, "", " ")pretty-prints; good for fixtures/config, not hot paths.- Maps marshal with sorted keys (deterministic output); struct fields marshal in declaration order.
- For streams (NDJSON, request bodies), prefer
json.NewEncoder(w).Encode(v)/json.NewDecoder(r).Decode(&v)— they read/write incrementally and avoid buffering the whole payload. TheDecoderreturnsio.EOFwhen the stream ends → [[json-streaming]]. - A pointer field encodes the pointed-to value, or JSON
nullif nil — handy to distinguish "absent" from "zero".
💻 Code Examples¶
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Admin bool `json:"-"` // never serialized
}
u := User{ID: 7, Name: "Ada", Admin: true}
b, _ := json.Marshal(u) // {"id":7,"name":"Ada"}
var got User
_ = json.Unmarshal(b, &got) // pointer destination, Admin stays false
Full code:
examples/month-02/json-tags/main.go· Run:go run ./examples/month-02/json-tags
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Encode/Decode a sensor Reading round trip |
✅ | exercises/month-02/week-2/tempjson |
🐛 Mistakes Made¶
- Passed a struct value (not
&v) toUnmarshal→json: Unmarshal(non-pointer ...). - Wondered why a lowercase
name stringfield never appeared in the output — unexported fields are skipped by the encoder. - Decoded a big integer ID into
interface{}and got afloat64with rounding — switched to a typedint64field.
❓ Open Questions¶
- When is
json.RawMessage(defer decoding part of a payload) the right tool vs. a secondUnmarshalpass?
🧠 Active Recall (answer without looking)¶
-
Q: Why must the destination of
json.Unmarshalbe a pointer?
A
Unmarshal needs to *write* the decoded values into your variable. Without a pointer it would only receive a copy, so the result would be discarded. Passing a non-pointer is a runtime error. -
Q: You decode
{"n": 42}intomap[string]interface{}. What is the Go type ofm["n"]?
A
`float64` — JSON has one numeric type, and the default mapping into `interface{}` is `float64`. Use a typed struct field or `Decoder.UseNumber()` to preserve integers exactly.
🪶 Feynman Reflection¶
JSON marshaling is a translator that walks your value with reflection and writes the equivalent text; unmarshaling reads that text back into a box you hand it by address. It only sees the public (exported) parts, fills in what it recognizes, and quietly skips the rest — lenient by default, strict only when you ask.
🕳️ Knowledge Gaps¶
json.RawMessageand partial/lazy decoding patterns.- Exactly when streaming with
DecoderbeatsUnmarshal(ReadAll(...)).
✅ Summary¶
I can round-trip Go values and JSON, know the encoder only touches exported fields, remember the number→float64 trap, and can stream with Encoder/Decoder.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 037: shape the wire format precisely with struct tags and custom
Marshaler/Unmarshaler.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): encoding/json marshal and unmarshal (day 036)