3.4 encoding/json:資料交換
encoding/json 是 Go 標準庫中負責 JSON 編碼與解碼的 package。它的核心用途是把 Go struct 轉成 JSON,或把 JSON 轉回 Go struct,讓程式能和設定檔、HTTP API、message queue 等外部格式交換資料。
本章目標
學完本章後,你將能夠:
- 用
json.Unmarshal解析 JSON bytes - 用
json.Marshal輸出 JSON bytes - 用
json.NewDecoder解析 stream - 用
json.NewEncoder寫出 response - 正確處理 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 解析成 struct | json.Unmarshal |
struct 轉成 []byte | json.Marshal |
從 io.Reader 讀 JSON | json.NewDecoder |
寫 JSON 到 io.Writer | json.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 裡,不直接暴露給使用者。