encoding/json 是 Go 標準庫中負責 JSON 編碼與解碼的 package。它的核心用途是把 Go struct 轉成 JSON,或把 JSON 轉回 Go struct,讓程式能和設定檔、HTTP API、message queue 等外部格式交換資料。

本章目標

學完本章後,你將能夠:

  1. json.Unmarshal 解析 JSON bytes
  2. json.Marshal 輸出 JSON bytes
  3. json.NewDecoder 解析 stream
  4. json.NewEncoder 寫出 response
  5. 正確處理 JSON 解析錯誤

【觀察】JSON 解碼是外部資料進入 Go 型別的邊界

JSON 解碼的核心規則是:外部資料必須先進入明確的 Go struct,後續程式才應依賴型別欄位。以下範例把設定檔 JSON 解析成 Config

 1type Config struct {
 2    AppName string `json:"appName"`
 3    Port    int    `json:"port"`
 4    Debug   bool   `json:"debug"`
 5}
 6
 7func LoadConfig(data []byte) (Config, error) {
 8    var cfg Config
 9    if err := json.Unmarshal(data, &cfg); err != nil {
10        return Config{}, fmt.Errorf("parse config JSON: %w", err)
11    }
12    return cfg, nil
13}

json.Unmarshal 需要接收 pointer,因為它要把解析結果寫入 cfg。若傳入 cfg 而不是 &cfg,解碼結果無法寫回呼叫端變數。

【判讀】JSON tag 是解碼與編碼的欄位對照表

JSON tag 的核心規則是:Go 欄位名稱和 JSON 欄位名稱可以不同,但必須在 struct tag 中明確對應。

1type User struct {
2    ID        string `json:"id"`
3    Name      string `json:"name"`
4    CreatedAt string `json:"createdAt"`
5}

這個 struct 對應的 JSON 是:

1{
2  "id": "u_1",
3  "name": "Alice",
4  "createdAt": "2026-04-22T10:00:00Z"
5}

Go 欄位必須 exported,encoding/json 才能讀寫。小寫開頭欄位是 unexported,JSON package 不會填入。

【策略】bytes 用 Marshal/Unmarshal,stream 用 Encoder/Decoder

JSON API 選擇的核心規則是:資料已經在記憶體中用 Marshal / Unmarshal,資料來自 stream 用 Encoder / Decoder

情境適合 API
[]byte 解析成 structjson.Unmarshal
struct 轉成 []bytejson.Marshal
io.Reader 讀 JSONjson.NewDecoder
寫 JSON 到 io.Writerjson.NewEncoder

HTTP request body 是 stream,適合用 decoder:

1func decodeCreateUser(r *http.Request) (CreateUserRequest, error) {
2    var req CreateUserRequest
3    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
4        return CreateUserRequest{}, fmt.Errorf("decode request JSON: %w", err)
5    }
6    return req, nil
7}

HTTP response writer 也是 stream,適合用 encoder:

1func writeJSON(w http.ResponseWriter, status int, data any) {
2    w.Header().Set("Content-Type", "application/json")
3    w.WriteHeader(status)
4    _ = json.NewEncoder(w).Encode(data)
5}

【執行】在 HTTP handler 中處理 JSON

HTTP JSON handler 的核心規則是:解析錯誤屬於 client input 問題,通常回 400;內部處理錯誤屬於 server 問題,通常回 500。

 1type CreateUserRequest struct {
 2    Name string `json:"name"`
 3}
 4
 5type CreateUserResponse struct {
 6    ID   string `json:"id"`
 7    Name string `json:"name"`
 8}
 9
10func handleCreateUser(w http.ResponseWriter, r *http.Request) {
11    var req CreateUserRequest
12    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
13        writeJSON(w, http.StatusBadRequest, map[string]string{
14            "error": "invalid JSON",
15        })
16        return
17    }
18
19    if req.Name == "" {
20        writeJSON(w, http.StatusBadRequest, map[string]string{
21            "error": "name is required",
22        })
23        return
24    }
25
26    resp := CreateUserResponse{
27        ID:   "u_1",
28        Name: req.Name,
29    }
30    writeJSON(w, http.StatusCreated, resp)
31}

這個 handler 把 JSON 邊界處理清楚:先解碼,再驗證,再執行核心邏輯,最後輸出 JSON。

設計檢查

忘記傳 pointer 給 Unmarshal

json.Unmarshal 必須把結果寫進目標值,所以目標要傳 pointer:

1var cfg Config
2err := json.Unmarshal(data, &cfg)

忽略 Decode 錯誤

JSON 來自外部輸入,解析錯誤是正常情境。忽略錯誤會讓後續程式拿到零值 struct,造成更難追蹤的 bug。

把內部錯誤直接回給外部

對外 response 應該穩定且安全;內部錯誤細節留在 log 或 error chain 裡,不直接暴露給使用者。