JSON 格式化完整教學 2026:從基礎到進階、整合 SuperTools 線上格式化

寫給:每天在 Postman、瀏覽器 DevTools、API 文件之間切換的前後端工程師、資料分析師,以及需要看懂 webhook payload 的 PM。

JSON(JavaScript Object Notation)是現代 Web 的通用語。從 REST API 的請求/回應、Kafka 訊息、設定檔,到 NoSQL 文件儲存,JSON 幾乎是預設選項。但每天打開一坨壓縮過、沒換行的 JSON 字串時,多數工程師仍然會皺眉。本文用 14 分鐘帶你完整掌握 JSON 格式化的核心觀念、實戰技巧、踩過的坑,以及如何用 SuperTools 線上工具在 10 秒內把醜陋的 payload 變成乾淨易讀的階層結構。

1. JSON 是什麼

JSON 由 Douglas Crockford 於 2001 年正式提出,2013 年成為 ECMA-404 標準、2017 年成為 RFC 8259。它的設計目標只有一個:人類可讀、機器可解析的最小資料交換格式。

JSON 的語法骨幹只有六個基本型別:

  • object:以 {} 包起來的 key-value 集合
  • array:以 [] 包起來的有序列表
  • string:以雙引號包起來的 Unicode 字串
  • number:整數或浮點數(不支援 NaN、Infinity)
  • boolean:true / false
  • null:空值

注意三件事:

  1. key 一定要用雙引號('key' 或不加引號都是 JavaScript 物件字面值、不是 JSON)
  2. 不允許尾隨逗號(trailing comma)
  3. 不允許註解(// 或 /* */)—— 想要註解請改用 JSON5、JSONC

合法的最小 JSON 範例:

{
  "name": "蘇曉雯",
  "age": 50,
  "skills": ["護膚", "創業"],
  "active": true,
  "spouse": null
}

JSON 在後端服務之間幾乎是預設交換格式,主要原因有三:

  • 結構直接對應到所有主流語言的 dict / map
  • 文字格式 debug 容易(不像 Protobuf 要 schema)
  • 體積在文字協定中相對小(比 XML 省 30%~60%)

2. 5 個核心觀念

2.1 字串編碼一定是 UTF-8

RFC 8259 明確規定 JSON 字串以 UTF-8 為傳輸編碼。中文、emoji、各國符號都直接寫進去即可,不需要轉成 中文。但若你的 API 客戶端使用較舊的 Java(< 17)或 Windows 系統,仍可能因為預設編碼錯誤而亂碼。最保險的做法是在 HTTP header 寫死 Content-Type: application/json; charset=utf-8。

import json

data = {"作者": "蘇曉雯", "標題": "五十歲也能逆襲"}
print(json.dumps(data, ensure_ascii=False))
# {"作者": "蘇曉雯", "標題": "五十歲也能逆襲"}

print(json.dumps(data, ensure_ascii=True))
# {"作者": "蘇曉雯", ...}

ensure_ascii=False 才能輸出原始中文,否則 Python 預設會跳脫成 \uXXXX。

2.2 數字精度有上限(IEEE 754 雙精度)

JSON 雖然允許任意精度的數字字面值,但幾乎所有解析器都會把 number 讀進 IEEE 754 64-bit 浮點數,這意味著:

  • 整數安全範圍只到 2^53 - 1 = 9007199254740991(JavaScript 的 Number.MAX_SAFE_INTEGER)
  • 浮點運算會有精度誤差,例如 0.1 + 0.2 !== 0.3

如果你的系統會傳超大整數(雪花 ID、區塊鏈金額、64-bit 時間戳),請改用字串:

{
  "tweet_id": "1234567890123456789",
  "amount_wei": "1000000000000000000"
}

JavaScript 端用 BigInt(str) 轉回大整數,Python 端用 int(str) 即可。

2.3 縮排(pretty print)與壓縮(minify)

JSON 格式化的兩個方向:

  • Pretty print:加換行 + 縮排,方便人類閱讀
  • Minify:去除所有空白與換行,方便網路傳輸

兩者語意完全等價、只差在 whitespace。慣例:

場景 建議
API response(生產環境) Minify(省頻寬)
設定檔、log、文件 Pretty print(2 space 縮排)
Git 追蹤的檔案 Pretty print + 排序 key(diff 容易看)
# 用 jq 美化
cat data.json | jq .

# 用 jq 壓縮
cat data.json | jq -c .

# 用 python
python3 -m json.tool data.json

2.4 JSON Pointer(RFC 6901)

當你要在巢狀 JSON 裡引用特定欄位時,用 JSON Pointer 標準路徑:

/users/0/email

對應到:

{
  "users": [
    {"email": "a@example.com"},
    {"email": "b@example.com"}
  ]
}

語法重點:

  • / 切分階層
  • 陣列用數字索引(從 0 起)
  • key 中本來就含 / 要跳脫成 ~1
  • key 中本來就含 ~ 要跳脫成 ~0

JSON Pointer 廣泛用於 JSON Patch、JSON Schema、OpenAPI 規範。

2.5 JSON Schema 驗證

JSON 本身不帶 schema,但工程化專案多會搭配 JSON Schema 做欄位驗證:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["name", "age"],
  "properties": {
    "name": {"type": "string", "minLength": 1},
    "age": {"type": "integer", "minimum": 0, "maximum": 150},
    "email": {"type": "string", "format": "email"}
  },
  "additionalProperties": false
}

主流語言都有實作(Python jsonschema、JS ajv、Go gojsonschema),可在 API 邊界自動拒絕格式錯誤的請求。

3. 實戰範例

範例 1:解析 GitHub API 回應

import json
import urllib.request

url = "https://api.github.com/repos/python/cpython"
with urllib.request.urlopen(url) as resp:
    raw = resp.read().decode("utf-8")

data = json.loads(raw)
print(f"專案:{data['full_name']}")
print(f"星數:{data['stargazers_count']}")
print(f"預設分支:{data['default_branch']}")

預期輸出:

專案:python/cpython
星數:61234
預設分支:main

範例 2:把巢狀 JSON 攤平成 CSV

import json
import csv

raw = '''
[
  {"id": 1, "user": {"name": "Alice", "city": "Taipei"}},
  {"id": 2, "user": {"name": "Bob", "city": "Tokyo"}}
]
'''

records = json.loads(raw)
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["id", "name", "city"])
    for r in records:
        writer.writerow([r["id"], r["user"]["name"], r["user"]["city"]])

範例 3:JavaScript 安全解析 + 例外處理

function safeParse(text) {
  try {
    return { ok: true, data: JSON.parse(text) };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

const result = safeParse('{"foo": 1,}'); // trailing comma 違規
if (!result.ok) {
  console.error("解析失敗:", result.error);
}

範例 4:用 jq 抓 nested 欄位

# 抓所有 user 的 email
curl -s https://jsonplaceholder.typicode.com/users | jq '.[].email'

# 過濾年齡 >= 30 的 user 名字
echo '[{"name":"A","age":25},{"name":"B","age":35}]' \
  | jq '.[] | select(.age >= 30) | .name'

範例 5:Go 自訂 marshaling

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Event struct {
    Name string    `json:"name"`
    At   time.Time `json:"at"`
}

func main() {
    e := Event{Name: "launch", At: time.Now()}
    b, _ := json.MarshalIndent(e, "", "  ")
    fmt.Println(string(b))
}

json.MarshalIndent 的第三個參數是縮排字串(兩個空白),輸出已 pretty print。

4. 進階技巧

4.1 串流解析(streaming)大檔案

當 JSON 檔案超過 100MB,整個讀進記憶體會 OOM。改用串流:

import ijson

with open("huge.json", "rb") as f:
    for item in ijson.items(f, "records.item"):
        process(item)  # 一次只在記憶體裡保留一筆

JavaScript 端用 stream-json 套件,Java 端用 Jackson JsonParser,都是 SAX-style event-based 解析。

4.2 排序 key 讓 diff 變乾淨

Git 追蹤 JSON 設定檔時,key 順序不穩會導致 diff 充滿假變更。永遠輸出已排序的 key:

json.dumps(data, indent=2, sort_keys=True, ensure_ascii=False)
jq -S . config.json > config.sorted.json

4.3 hash 算 JSON 內容是否相同

兩個語意相同但字串不同的 JSON(key 順序、空白)應該判定相等。先正規化再算 hash:

import hashlib, json

def json_hash(obj):
    canonical = json.dumps(obj, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()

separators=(",", ":") 去除所有 whitespace,加上 sort_keys=True 後就是 RFC 8785 風格的 canonical form。

4.4 JSON Patch 部分更新

REST API 支援部分更新時,傳遞 JSON Patch(RFC 6902)比整包覆蓋更安全:

[
  { "op": "replace", "path": "/email", "value": "new@example.com" },
  { "op": "add", "path": "/tags/-", "value": "vip" },
  { "op": "remove", "path": "/temp_field" }
]

伺服器收到後用 jsonpatch 套件套用即可。

4.5 NDJSON / JSON Lines

當你要寫 log 或 streaming dataset,一行一個 JSON 物件比包成大陣列更有彈性:

{"ts": "2026-06-05T10:00:00Z", "level": "info", "msg": "start"}
{"ts": "2026-06-05T10:00:01Z", "level": "warn", "msg": "slow"}

副檔名通常為 .ndjson 或 .jsonl。

# 用 jq 處理 NDJSON
cat events.jsonl | jq -c 'select(.level == "warn")'

5. 常見錯誤與解法

錯誤 1:Unexpected token } in JSON at position N

症狀:JS 解析時 throw SyntaxError。 根因:通常是尾隨逗號({"a":1,})或 key 沒加引號。 解法:用線上 lint 工具標出錯誤位置;或在開發階段改用 JSON5 容忍尾隨逗號與註解。

錯誤 2:中文變成 中文

症狀:API 回應中文全變跳脫序列。 根因:序列化時開了 ensure_ascii=True(Python 預設)或 Java 用 Jackson 沒設 WRITE_NUMBERS_AS_STRINGS。 解法:Python 用 json.dumps(..., ensure_ascii=False);Java Jackson 設 mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false)。

錯誤 3:大整數變成科學記號 / 失去精度

症狀:傳 1234567890123456789 給前端,JS 顯示 1234567890123456800。 根因:JavaScript Number 是 IEEE 754,安全整數上限 2^53 - 1。 解法:API 把這類 ID 改傳字串、或 client 用 JSON.parse(text, (key, value) => ...) 的 reviver 攔截 BigInt 欄位。

錯誤 4:浮點數比較失敗

症狀:JSON 來回反序列化後 0.1 + 0.2 !== 0.3。 根因:所有 JSON 解析器都用 IEEE 754,浮點精度誤差先天存在。 解法:金融金額一律存 cents(整數)或字串;比較時用 epsilon Math.abs(a - b) < 1e-9。

錯誤 5:Pretty print 後變大 4 倍

症狀:API gzip 前後體積差很多。 根因:意外把 minify 改成 indent=2。 解法:生產環境 response 一律 minify;開發環境 / log 才 pretty print。在 Nginx 開 gzip 後差異會大幅縮小,但仍建議 minify。

6. 用 SuperTools 線上版完成相同任務

不想 pip install 或開 jq?打開 SuperTools JSON 格式化工具,貼上 JSON 直接:

  • Format:一鍵縮排 2/4 spaces,並 highlight 語法
  • Minify:把空白全部去掉,產出最小體積
  • Validate:紅字標出 syntax error 行號
  • Sort keys:排序所有 key(巢狀也適用)
  • JSON ↔ YAML:互轉用於 K8s manifest

工具完全在 client-side 跑(瀏覽器 WebAssembly),你的 JSON 不會上傳到伺服器,可放心貼公司內部 API payload。

實際使用流程:

  1. 從 Postman 或 Chrome DevTools 複製 response body
  2. 開 supertools.tw/dev/json-formatter 貼上左側
  3. 按「Format」右側看美化結果
  4. 點任一行最左側 ▶ 摺疊節點,快速跳過長陣列

立即使用 SuperTools JSON 格式化工具:https://supertools.tw/dev/json-formatter

7. 其他語言實作

Python(標準庫)

import json
text = json.dumps({"k": "v"}, indent=2, ensure_ascii=False)
obj = json.loads('{"k": "v"}')

JavaScript / TypeScript

const text = JSON.stringify({k: "v"}, null, 2);
const obj = JSON.parse('{"k": "v"}');

Go

b, _ := json.MarshalIndent(map[string]string{"k": "v"}, "", "  ")
var obj map[string]string
json.Unmarshal([]byte(`{"k":"v"}`), &obj)

Rust(serde_json)

use serde_json::{json, to_string_pretty};
let v = json!({"k": "v"});
let s = to_string_pretty(&v).unwrap();

Bash(jq)

echo '{"k":"v"}' | jq .

8. FAQ

Q1:JSON 跟 JSON5、JSONC、HJSON 差別? JSON5 / JSONC 允許註解 + 尾隨逗號,常用在前端設定檔(VSCode settings.json 就是 JSONC)。HJSON 更寬鬆但相容性差。生產 API 一律用標準 JSON。

Q2:JSON 可以放二進位嗎? 不能直接放。請 base64 編碼後當字串塞進去(見 Base64 編碼完整指南)。

Q3:JSON vs Protobuf vs MessagePack? JSON 文字、易讀但體積大;Protobuf / MessagePack 二進位、體積小但需 schema。內部高頻服務改 Protobuf;對外 API 用 JSON 比較友善。

Q4:要支援 JSONP 嗎? 2026 年幾乎不需要。所有現代瀏覽器都支援 CORS,JSONP 已過時且有安全風險。

Q5:JSON 檔案要不要 BOM? 不要。RFC 8259 明確規定 JSON 不應該以 BOM 開頭,雖然解析器多會容忍。寫檔時記得用 utf-8(不是 utf-8-sig)。

9. 實戰案例研究

案例 1:跨服務 ID 精度遺失

某社群 app 後端 Go service 用 int64 ID(雪花算法),前端 React 收到 JSON 解析後最後 3 位數字總是 000。

debug:

  1. ID 是 1924850384903412345,超過 JS Number.MAX_SAFE_INTEGER (9007199254740991)
  2. JSON.parse 強制轉 Number,精度丟失
  3. 後端 log 顯示傳 1924850384903412345,前端收到 1924850384903412000

修法:

  1. 後端 marshal 時把所有 int64 ID 轉字串:json:"id,string"
  2. 前端用 BigInt(s) 處理或全程當字串
  3. 跨服務通訊 contract 明確規範

教訓:所有超過 53 bit 的整數,跨 JSON 邊界必須變字串。

案例 2:JSON Schema 救援錯誤 user input

某 SaaS 接 webhook,期望 user 傳特定 schema 的 JSON。某天大量 webhook 噴 500,調查發現 user 改了 schema 欄位型別(從 number 變字串),下游程式 panic。

修法:

  1. 接收端加 JSON Schema 驗證(jsonschema 套件)
  2. 在 webhook handler 第一行 validate,schema 不符直接 400
  3. 提供清晰錯誤訊息 + schema doc link
  4. 加 unit test 涵蓋 5 種典型錯誤 schema

結果:service 從每天 200 次 500 → 0,user 也學會看錯誤訊息修自己的 webhook。

案例 3:大檔 JSON 串流解析

某分析平台每月匯入 5GB 的 JSON 資料(一個大陣列 100 萬筆 record)。原本一次 json.load(f) 吃 12GB RAM 直接 OOM。

改用 ijson 串流:

import ijson
with open("data.json", "rb") as f:
    for record in ijson.items(f, "item"):
        process(record)

RAM 用量從 12GB → 80MB,處理時間從 OOM crash → 12 分鐘完成。

教訓:JSON 超過 100MB 必用串流解析。

10. 結論 + 延伸閱讀

JSON 看似簡單,但魔鬼藏在編碼、精度、Schema 驗證、串流解析這幾個細節裡。掌握本文 5 個核心觀念 + 5 個實戰範例後,你已經比 80% 的工程師更懂 JSON。下次面對亂碼 payload 時,打開 SuperTools 線上工具 10 秒搞定,把時間留給更值得的事。三條黃金法則:大整數變字串、邊界一定 schema validate、大檔走串流。

延伸閱讀: