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

結論:編輯器按場景選——工具站與移動端用 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());
}
}),
],
});
四個要點:
json()提供語法高亮與括號匹配,不要自己寫;jsonParseLinter()給出行列級錯誤標記,配合lintGutter()在行號旁顯示圖示;updateListener必須防抖,每次按鍵都JSON.parse十萬行會明顯示卡;- 大文件要開
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 不該敏感,但現實裡踩過坑)。可以單獨排除。
五、除錯技巧
jq -e當斷言用:表示式結果為false或null時退出碼為 1,可直接寫進指令碼做檢查;jq 'paths'列出所有路徑,快速瞭解陌生文件的結構;jq '.. | objects | keys'找出所有出現過的鍵名,適合排查拼寫不一致;- 編輯器裡先格式化再對比:未格式化的 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。