JSON 工具箱

編輯器、命令列與自動化工具鏈

結論:編輯器解決「看得清、改得對」,命令列解決「批次、可重複」。兩邊各有幾個能用十年的工具。

入門約 10 分鐘發佈於 2026-10-30更新於 2026-10-30
編輯器、命令列與自動化工具鏈封面

結論:編輯器按場景選——工具站與移動端用 CodeMirror 6,需要完整 IDE 體驗才上 Monaco;批次與自動化交給 jq 加語法/結構/風格三層 CI 校驗,指令碼傳參一律用 jq --arg。

手工檢查 JSON 的時代該結束了:能讓機器重複做的事,不要靠人眼。

一、瀏覽器裡的編輯器

1.1 CodeMirror 6 的最小整合

import { EditorView, basicSetup } from 'codemirror';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import { linter, lintGutter } from '@codemirror/lint';

const view = new EditorView({
  parent: document.getElementById('editor')!,
  doc: '{"hello": "json"}',
  extensions: [
    basicSetup,
    json(),
    linter(jsonParseLinter()),
    lintGutter(),
    EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        // 防抖後再做重活,別在每次按鍵時格式化
        scheduleParse(update.state.doc.toString());
      }
    }),
  ],
});

四個要點:

  1. json() 提供語法高亮與括號匹配,不要自己寫;
  2. jsonParseLinter() 給出行列級錯誤標記,配合 lintGutter() 在行號旁顯示圖示;
  3. updateListener 必須防抖,每次按鍵都 JSON.parse 十萬行會明顯示卡;
  4. 大文件要開 EditorView.lineWrapping 之外的最佳化,避免一次性渲染整篇(CodeMirror 6 預設按視口渲染,這點比舊版好)。

1.2 摺疊與大綱

CodeMirror 6 內建摺疊能力,JSON 場景下常按「物件/陣列」摺疊:

import { foldGutter, foldKeymap } from '@codemirror/language';
import { keymap } from '@codemirror/view';

配上 foldGutter() 與 keymap.of(foldKeymap),使用者可以用快捷鍵收起長陣列。

1.3 與 Monaco 的取捨

維度 CodeMirror 6 Monaco
體積 小,模組化 大(數 MB)
移動端 支援較好 體驗一般
語言服務 需自建 內建豐富
定製成本 低 中

工具站、移動端優先 → CodeMirror;需要完整的 IDE 體驗(多檔案、跳轉、補全)→ Monaco。

二、命令列:jq 的六個高頻配方

jq 是 JSON 領域的 sed + awk,語法只學一小部分就能覆蓋八成日常需求。

# 1. 取欄位(最常用)
jq '.data.items[].name' response.json

# 2. 過濾 + 重組
jq '.data.items[] | select(.price < 10) | {id, price}' response.json

# 3. 統計(長度、求和、最大)
jq '[.items[] | .price] | {count: length, total: add, max: max}' response.json

# 4. 分組聚合
jq '[.items[]] | group_by(.category) | map({category: .[0].category, count: length})' response.json
# 5. 合併多個檔案成陣列
jq -s '.' part-*.json > merged.json

# 6. 逐行處理 JSON Lines
jq -c 'select(.level == "error")' app.jsonl

# 常用輸出開關
jq -r '.data.name'      # 去掉引號,方便管道給 shell
jq -c '.'               # 緊湊輸出,一行
jq --arg id "$ID" '.id == $id'   # 安全傳參,避免拼接注入

--arg 這一條值得單獨記:直接把變數拼進表示式會被內容裡的引號破壞,--arg 由 jq 負責轉義。

2.1 其他工具一覽

工具 用途 一句話
yq YAML/JSON/XML 互轉 語法與 jq 類似,配置類檔案主力
dasel 多格式統一查詢 支援 JSON/YAML/TOML/XML/CSV
fx 互動式 JSON 瀏覽 終端裡點點看,適合探索
jless 終端分頁瀏覽 類似 less,支援摺疊與搜尋
gron 把 JSON 攤平成可 grep 的行 排查「這個欄位在哪一層」利器
ajv-cli schema 校驗 CI 裡校驗 JSON 檔案

gron 的思路特別值得知道:

$ echo '{"a":{"b":[1,2]}}' | gron
json = {};
json.a = {};
json.a.b = [];
json.a.b[0] = 1;
json.a.b[1] = 2;

攤平之後 grep 就能定位路徑,比在編輯器裡逐層展開快得多。

三、CI 裡的三層校驗

層次 工具 失敗意味著
語法 JSON.parse / jq empty 檔案本身寫壞了
結構 ajv-cli + schema 欄位缺失、型別不對
風格 Prettier 格式不統一,diff 噪音大
# 語法:批次校驗倉庫裡所有 json
find . -name '*.json' -not -path './node_modules/*' -print0 \
  | xargs -0 -n1 jq empty

# 結構:按 schema 校驗
npx ajv-cli validate -s schemas/order.json -d 'fixtures/orders/*.json' --strict=false

# 風格:只檢查不修改
npx prettier --check "**/*.json"

三層都要有,因為它們的錯誤資訊完全不同: jq empty 只說「哪一行壞了」,ajv 說「哪個欄位不符合哪個關鍵字」,Prettier 只說「格式不一致」。

四、pre-commit 鉤子

# .husky/pre-commit 示例(節選)
npx lint-staged
{
  "lint-staged": {
    "*.json": ["prettier --write", "jq empty"]
  }
}

注意 package.json、tsconfig.json 這類工具配置檔案不要輕易讓 Prettier 重排縮排—— 某些工具對格式敏感(雖然合法 JSON 不該敏感,但現實裡踩過坑)。可以單獨排除。

五、除錯技巧

  1. jq -e 當斷言用:表示式結果為 false 或 null 時退出碼為 1,可直接寫進指令碼做檢查;
  2. jq 'paths' 列出所有路徑,快速瞭解陌生文件的結構;
  3. jq '.. | objects | keys' 找出所有出現過的鍵名,適合排查拼寫不一致;
  4. 編輯器裡先格式化再對比:未格式化的 diff 全是噪音,/diff 類結構化對比更準。

六、檢查清單

  • [ ] 編輯器錯誤標記精確到行列,且解析做了防抖;
  • [ ] 團隊指令碼統一用 jq --arg 傳參,不拼字串;
  • [ ] CI 三層校驗齊全(語法/結構/風格);
  • [ ] pre-commit 只處理暫存檔案,避免全量掃描拖慢提交;
  • [ ] 工具版本在 package.json 或 .tool-versions 中鎖定。

常見問題

如何在 CodeMirror 6 與 Monaco 之間選擇?

工具站、移動端優先選 CodeMirror 6:體積小、模組化、移動端支援較好,語法高亮、括號匹配與摺疊都能按需組合。需要多檔案、跳轉、補全這類完整 IDE 體驗時才上 Monaco,代價是數 MB 的體積與更高的定製成本。

為什麼 CI 裡要同時保留語法、結構與風格三層校驗?

三層報錯資訊完全不同:jq empty 只說哪一行壞了,ajv 說哪個欄位不符合哪個關鍵字,Prettier 只說格式不一致。缺一層就會讓另一層的錯誤被誤讀,所以三層都要有,且前一層失敗時立即停止。

能否只靠 pre-commit 而不在 CI 裡校驗?

不能只靠 pre-commit。鉤子只處理暫存檔案,能在一分鐘內給回饋,但無法覆蓋全量與繞過提交的改動;CI 負責求全。常見做法是 pre-commit 只跑暫存檔案,全量掃描放在 CI。