Lesson 23 — JSON Encoding với encoding/json

3 module dưới đây minh hoạ (1) tag struct ảnh hưởng output JSON ra sao, (2) quá trình Unmarshal match field giữa JSON và struct, (3) sự khác biệt RAM giữa ReadAll + Unmarshal (load-all) và NewDecoder + Decode (stream) trên file 100MB.

1. Struct ↔ JSON — toggle tag, xem output live

Bên trái: struct Go với tag điều chỉnh được. Bên phải: JSON output do json.Marshal sinh ra. Đổi tag / giá trị, output cập nhật tức thì.

Go struct type User struct {
}
json.Marshal output
Bật/tắt option để xem ảnh hưởng tới JSON.
Lưu ý: omitempty chỉ bỏ field khi nó là zero value của kiểu Go (vd "", 0, false, nil). Với struct rỗng (vd time.Time{}) thì không — đây là pitfall phải dùng pointer.

2. Unmarshal flow — animate decode field-by-field

Chọn 1 trong các JSON input bên dưới, click "▶ Decode từng bước" để xem json.Unmarshal match từng key với field của struct. Field khớp → xanh; field struct thiếu trong JSON → đỏ; key JSON không có field tương ứng → cam.

JSON input
Struct target (sau decode)
Chọn case rồi nhấn Decode.
khớp struct thiếu (JSON không có) JSON dư (struct không có) đang xử lý
Behavior mặc định của Go: field dư trong JSON → silent ignore (không lỗi). Field struct không có trong JSON → giữ zero value. Nếu bật dec.DisallowUnknownFields() thì case "JSON dư" sẽ trả lỗi.

3. Stream vs Load-all — RAM khi parse file 100MB

Giả lập đọc file NDJSON 100MB (1 triệu user JSON). So sánh 2 cách: Load-all (io.ReadAll + json.Unmarshal) — toàn bộ file vào RAM trước khi parse; vs Stream (json.NewDecoder.Decode) — đọc nhỏ giọt, chỉ giữ 1 object tại 1 thời điểm.

❌ Load-all: ReadAll + Unmarshal

0 MB
RAM peak: 0 MB
Time: 0 ms
Status: idle

✅ Stream: NewDecoder.Decode

0 MB
RAM peak: 0 MB
Time: 0 ms
Status: idle
Trục RAM scale theo file size. Bạn sẽ thấy Load-all tăng tuyến tính theo kích thước file (RAM spike = full file size), trong khi Stream giữ phẳng ở mức ~vài chục KB bất kể file lớn cỡ nào.
Tại sao quan trọng? Server có 1GB RAM, nhận request upload JSON 600MB. Load-all → out-of-memory crash. Stream → chạy thoải mái. Đây là lý do mọi HTTP framework dạy json.NewDecoder(r.Body).Decode(&req), không phải io.ReadAll(r.Body) trước.