三板斧:jq、xmllint與自建腳本實(shí)戰(zhàn))
簡(jiǎn)介面向開發(fā)者的輕量JSON/XML格式化與比對(duì)工具包適合前端、后端及測(cè)試人員在日常接口調(diào)試、配置核查、日志分析中快速校驗(yàn)數(shù)據(jù)格式、定位錯(cuò)誤并對(duì)比差異。工具提供實(shí)時(shí)格式校驗(yàn)?zāi)軠?zhǔn)確顯示錯(cuò)誤所在行號(hào)與列號(hào)一鍵格式化、復(fù)制和清空功能讓數(shù)據(jù)整理更高效JSON比對(duì)模塊采用雙面板設(shè)計(jì)支持行號(hào)顯示、層級(jí)折疊、差異可視化分析并可交換左右數(shù)據(jù)、窗口最大化便于處理復(fù)雜嵌套結(jié)構(gòu)。壓縮包約37KB共7個(gè)文件以HTML、CSS、JavaScript為主其中HTML/CSS負(fù)責(zé)頁面與界面樣式JS實(shí)現(xiàn)格式化與差異比對(duì)核心邏輯另附JSON配置、PNG圖標(biāo)及Markdown說明文檔整體結(jié)構(gòu)清晰既可直接使用也便于參考改造。已有244人學(xué)習(xí)下載適合需要頻繁處理接口數(shù)據(jù)、配置文件和日志的開發(fā)人員使用。1. 為什么這三個(gè)小工具值得花時(shí)間折騰json 格式化工具、xml 格式化工具、json 數(shù)據(jù)比對(duì)工具這三個(gè)東西單獨(dú)看都是小工具湊在一起就是一套本地?cái)?shù)據(jù)處理三板斧。接口聯(lián)調(diào)時(shí)最磨人的場(chǎng)景不是邏輯寫錯(cuò)而是兩邊各返回一坨擠成一行、沒有換行的 JSON 和 XML肉眼盯十分鐘也看不出差異等你把差異找到了往往發(fā)現(xiàn)只是鍵的順序不同根本不是 bug。這套三件套解決的就是這類問題先格式化把結(jié)構(gòu)攤開再用可靠的比對(duì)方式找出真實(shí)差異最后把同一個(gè)流程固化成一個(gè)命令下次遇到直接跑。這套東西適合誰后端排查接口響應(yīng)、前端聯(lián)調(diào) mock 數(shù)據(jù)、數(shù)據(jù)工程師檢查配置文件、以及所有被肉眼 diff折磨過的人。不需要裝大型 IDE命令行和一個(gè)小腳本就夠用。下面我把原理、最小命令、自建比對(duì)腳本和踩過的坑按順序講清楚。2. 格式化與比對(duì)的底層邏輯先搞清楚工具在替你做什么2.1 JSON 格式化到底在格式化什么JSON 格式化不是簡(jiǎn)單地在逗號(hào)后面加個(gè)換行它背后一定有一個(gè)解析器先把文本讀成內(nèi)存里的結(jié)構(gòu)體再把這個(gè)結(jié)構(gòu)體重新序列化輸出。這個(gè)過程至少有四件事縮進(jìn)排版、鍵序調(diào)整可選、字符串轉(zhuǎn)義還原、以及最重要的語法校驗(yàn)。一段非法 JSON讓格式化工具跑一遍會(huì)直接報(bào)錯(cuò)所以格式化工具天然是校驗(yàn)工具。理解這一點(diǎn)你就明白為什么格式化和查詢經(jīng)常出現(xiàn)在同一個(gè)工具里。像 jq 這種命令行的 json 查詢函數(shù)本質(zhì)是在解析后的結(jié)構(gòu)體上做取值、過濾和映射而不是像正則那樣硬啃字符串。很多人遇到spark 中讀取 json 報(bào)錯(cuò)的第一反應(yīng)是去改 SQL其實(shí)更快的辦法是先把 source 文件拿下來格式化一遍看 JSON 解析器在哪一行吐因?yàn)?spark 讀 json 時(shí)用的也是解析器解析失敗的原因絕大多數(shù)是被引號(hào)、末尾逗號(hào)或壞 Unicode 搞崩的。另一個(gè)容易忽略的點(diǎn)JSON 的鍵順序在語義上沒有意義{a:1,b:2}和{b:2,a:1}是同一個(gè)對(duì)象。所以一個(gè)合格的格式化工具應(yīng)該允許你選擇是否排序鍵而一個(gè)合格的比對(duì)工具應(yīng)該默認(rèn)忽略鍵序差異。這個(gè)原則貫穿后面所有操作。2.2 XML 格式化與標(biāo)簽語義dom4j 那套規(guī)則XML 格式化比 JSON 麻煩一點(diǎn)因?yàn)?XML 除了節(jié)點(diǎn)嵌套還有屬性、命名空間、CDATA、實(shí)體引用這些東西。格式化工具要做的是調(diào)整文本排版但絕不能動(dòng)標(biāo)簽的配對(duì)關(guān)系。你寫一個(gè) Java 程序用 dom4j 解析 XML步驟無非是加載 Document、遍歷 Node、再重新輸出命令行里的 XML 格式化工具做的也是同一件事只是把重新輸出這一步變成了帶縮進(jìn)的序列化。所以我建議你把格式化工具當(dāng)成一個(gè)解析器的排版外皮來用它能排版前提是文件能被解析。經(jīng)常有人說xml 格式文件沒有標(biāo)簽怎么辦這種文件本質(zhì)上是純文本標(biāo)簽都沒了說明它或者根本不是 XML或者標(biāo)簽在傳輸中被某個(gè)環(huán)節(jié)吃掉了。格式化工具不能憑空補(bǔ)標(biāo)簽它只能幫你把已有的結(jié)構(gòu)攤開。先認(rèn)清這一點(diǎn)排查方向就不會(huì)跑偏。XML 里還有一個(gè)容易踩的語義細(xì)節(jié)屬性順序不影響語義CDATA 里的內(nèi)容則是原樣保留的。所以一個(gè)可靠的 XML 格式化工具必須做到兩點(diǎn)——不重排屬性順序不破壞 CDATA 內(nèi)容。市面上有些在線工具會(huì)把屬性按字典序重排看起來整齊了實(shí)際上改變了原始文檔的指紋做簽名校驗(yàn)時(shí)就會(huì)翻車。2.3 比對(duì)工具的核心文本 diff 與結(jié)構(gòu) diff 的差距先說文本 diff就是diff命令干的事逐字節(jié)或逐行比較找到第一處不同就報(bào)出來。對(duì)代碼文件它很合適對(duì) JSON 和 XML 就經(jīng)常誤報(bào)。兩個(gè) JSON 語義完全一樣只要鍵順序不同diff會(huì)報(bào)出一大片紅色兩個(gè) XML 只要標(biāo)簽間的空白文本節(jié)點(diǎn)不一樣也會(huì)到處標(biāo)紅。結(jié)構(gòu) diff 是另一條路先把兩邊分別解析成樹再遞歸比較對(duì)應(yīng)節(jié)點(diǎn)。這樣鍵序變化不會(huì)誤報(bào)數(shù)字 1 和 1.0 也能靠類型規(guī)則區(qū)分。市面上好的 json 數(shù)據(jù)比對(duì)工具都走結(jié)構(gòu) diff但它比文本 diff 貴——要先完整解析而且對(duì)數(shù)組順序怎么處理是個(gè)策略問題是把數(shù)組當(dāng)成有序列表逐項(xiàng)比較還是當(dāng)成無序集合做匹配大多數(shù)場(chǎng)景里數(shù)組是有序的我建議比對(duì)工具默認(rèn)按順序比。選型上我的建議是分三層最小范圍用jq -S排序后接diff這能覆蓋七成簡(jiǎn)單場(chǎng)景復(fù)雜嵌套結(jié)構(gòu)用自寫遞歸比對(duì)腳本能輸出精確的差異路徑實(shí)在要給人看的可視化比對(duì)才上圖形化工具。不要一上來就裝大客戶端命令行三件套在你服務(wù)器上也能用。3. 用 jq 和 xmllint 跑通格式化最小命令與參數(shù)對(duì)照3.1 JSON 格式化最小命令jq 的縮進(jìn)與排序參數(shù)如果你的系統(tǒng)里有 jq沒有的話包管理器直接裝格式化就是一條命令的事。先看最小命令# 格式化輸出到標(biāo)準(zhǔn)輸出不做鍵排序 jq . raw.json # 按鍵名排序后再格式化常用于比對(duì)前的預(yù)處理 jq -S . raw.json # 自定義縮進(jìn)為 4 個(gè)空格讀取 json 數(shù)組文件也一樣適用 jq --indent 4 . raw.json邏輯說明.是 jq 的過濾器表示原樣取出整個(gè)文檔。jq 拿到這個(gè)過濾器后會(huì)把輸入解析成內(nèi)部結(jié)構(gòu)再按默認(rèn)的 2 空格縮進(jìn)輸出所以jq .就是最基礎(chǔ)的格式化命令。-S是--sort-keys的簡(jiǎn)寫會(huì)遞歸地按鍵名排序這個(gè)參數(shù)在比對(duì)場(chǎng)景里是核心——前面說過鍵序不影響語義排序后再 diff 就能消除一類假差異。--indent 4是給喜歡 4 空格縮進(jìn)的人準(zhǔn)備的不影響語義。參數(shù)說明如果只想校驗(yàn)不想看輸出用jq -e . file /dev/null-e會(huì)讓 jq 在解析失敗時(shí)返回非零退出碼方便寫進(jìn)腳本判斷。注意 jq 默認(rèn)輸出 UTF-8不會(huì)把中文轉(zhuǎn)成\uXXXX這點(diǎn)和后面要講的 Pythonjson.dumps正好相反。3.2 XML 格式化最小命令xmllint 的縮進(jìn)與編碼參數(shù)XML 這邊推薦xmllint它來自 libxml2 工具集基本是 Linux 和 macOS 都會(huì)自帶的。最小命令# 格式化輸出到標(biāo)準(zhǔn)輸出 xmllint --format raw.xml # 格式化并寫回另一個(gè)文件原文件保持不變 xmllint --format raw.xml --output pretty.xml # 指定輸出編碼避免中文亂碼 xmllint --format raw.xml --encode UTF-8 --output pretty.xml邏輯說明--format會(huì)重新縮進(jìn)所有節(jié)點(diǎn)把自閉合標(biāo)簽、換行、縮進(jìn)統(tǒng)一處理掉同時(shí)保留屬性順序和 CDATA 內(nèi)容。--output指定寫回的文件這是一個(gè)好習(xí)慣——不要讓工具直接覆蓋原文件萬一格式化失敗原文件還在。--encode UTF-8是處理中文的重要參數(shù)如果你的 XML 文件是 GBK 編碼不加這個(gè)參數(shù)輸出到終端會(huì)亂碼。參數(shù)說明如果你只是想校驗(yàn) XML 合法性用xmllint --noout file.xml它只報(bào)錯(cuò)不輸出。和 JSON 一樣格式化工具同時(shí)是校驗(yàn)工具xmllint遇到多個(gè)根節(jié)點(diǎn)、未閉合標(biāo)簽會(huì)直接報(bào)錯(cuò)并返回非零退出碼。順手提一句如果你在寫 Java用 dom4j 解析 XML 的步驟里最后 serializer 輸出的就是格式化后的文本本質(zhì)上和 xmllint 做的是同一件事。3.3 批量格式化與寫回文件先臨時(shí)文件再覆蓋單文件格式化學(xué)會(huì)后自然要做批量。這里有一個(gè)我踩過的坑千萬別直接在原文件上重定向。下面這個(gè)模式是安全的# 批量格式化當(dāng)前目錄下所有 json 文件 for f in *.json; do jq -S . $f $f.tmp mv $f.tmp $f done # 批量格式化所有 xml 文件先臨時(shí)文件再覆蓋 for f in *.xml; do xmllint --format $f --output $f.tmp mv $f.tmp $f done邏輯說明第一條命令把格式化結(jié)果寫到臨時(shí)文件只有 jq 成功退出保證退出碼為 0才用mv覆蓋原文件。這樣即使某個(gè)文件解析失敗原文件也不會(huì)被一個(gè)空文件或半截輸出覆蓋。第二個(gè)循環(huán)同理xmllint的--output指定臨時(shí)文件名成功后才覆蓋。參數(shù)說明批量處理前先確認(rèn)所有文件編碼一致混著 GBK 和 UTF-8 的目錄會(huì)讓這組命令產(chǎn)生亂碼輸出。另外這兩條命令對(duì)子目錄不生效需要遞歸的話把*.json換成find . -name *.json配合while read循環(huán)。這種先臨時(shí)文件再覆蓋的習(xí)慣能幫你省掉很多后悔藥。實(shí)際使用中我從網(wǎng)上下載接口樣例或 json 格式文件時(shí)第一步都是先跑一遍格式化看看結(jié)構(gòu)。在 spark 中讀取 json 之前也一樣先把下載的雜亂的 json 文件用jq .洗一遍能直接從報(bào)錯(cuò)行判斷壞數(shù)據(jù)在哪比在分布式環(huán)境里反復(fù) job 失敗快得多。4. 自建 JSON 數(shù)據(jù)比對(duì)腳本遞歸比對(duì)與差異路徑輸出4.1 為什么不能直接 diff鍵序與空白帶來的假差異先做一個(gè)實(shí)驗(yàn)兩個(gè)語義完全相同的 JSONdiff會(huì)怎樣報(bào)看下面這段echo {name:a,age:18} | jq -S . left.json echo {age:18,name:a} | jq -S . right.json diff left.json right.json正常執(zhí)行后沒有任何輸出因?yàn)閖q -S把兩個(gè)文件的鍵都排成了age在前、name在后文本完全一致。但如果去掉-S直接 diff你會(huì)看到兩行全被標(biāo)紅。這就是鍵序帶來的假差異。另一種假差異來自空白一個(gè)文件是 2 空格縮進(jìn)另一個(gè)是 4 空格縮進(jìn)diff會(huì)把每一行都當(dāng)成不同。所以我的結(jié)論是文本 diff 只能用于已知兩邊已被相同規(guī)則格式化過的 JSON。滿足這個(gè)前提后jq -S加diff是最快的冒煙比對(duì)方式。但它有個(gè)致命盲區(qū)——數(shù)組順序不同時(shí)它只會(huì)告訴你在哪一行不同而不會(huì)告訴你哪個(gè)元素在左邊有右邊沒有。這個(gè)問題需要結(jié)構(gòu)比對(duì)來解決。4.2 Python 遞歸比對(duì)腳本處理嵌套、數(shù)組與類型我自用的比對(duì)腳本核心是一個(gè)遞歸函數(shù)輸出的是可讀的差異路徑比如root.items[2].price。下面是這個(gè)腳本的完整版import json import sys def diff_json(a, b, pathroot): # bool 是 int 的子類先排除避免 True 和 1 被判為同類 if isinstance(a, bool) ! isinstance(b, bool): yield f{path}: type {type(a).__name__} ! {type(b).__name__} return # 數(shù)字類型統(tǒng)一成 float 比較1 和 1.0 視為相等 if isinstance(a, (int, float)) and isinstance(b, (int, float)): if float(a) ! float(b): yield f{path}: {a!r} ! {b!r} return if type(a) ! type(b): yield f{path}: type {type(a).__name__} ! {type(b).__name__} return if isinstance(a, dict): for k in sorted(set(a.keys()) | set(b.keys())): if k not in a: yield f{path}.{k}: missing in left elif k not in b: yield f{path}.{k}: missing in right else: yield from diff_json(a[k], b[k], f{path}.{k}) elif isinstance(a, list): if len(a) ! len(b): yield f{path}: length {len(a)} ! {len(b)} for i, (x, y) in enumerate(zip(a, b)): yield from diff_json(x, y, f{path}[{i}]) else: if a ! b: yield f{path}: {a!r} ! {b!r} if __name__ __main__: if len(sys.argv) ! 3: print(usage: jsondiff.py left.json right.json) sys.exit(2) with open(sys.argv[1], encodingutf-8) as f1, \ open(sys.argv[2], encodingutf-8) as f2: left json.load(f1) right json.load(f2) diffs list(diff_json(left, right)) if diffs: print(\n.join(diffs)) sys.exit(1) print(same)邏輯說明函數(shù)從根節(jié)點(diǎn)開始遞歸遇到 dict 就取兩邊鍵的并集排序后逐個(gè)比較缺失的鍵單獨(dú)報(bào)遇到 list 先比長(zhǎng)度再按索引逐項(xiàng)比較其余類型直接比值。所有差異都用生成器yield拋出來主函數(shù)統(tǒng)一收集后打印。退出碼是 1 表示有差異0 表示相同方便接進(jìn) shell 腳本。參數(shù)說明腳本第 10 行到第 15 行處理了最常見的一類誤報(bào)——1和1.0在 Python 里分別被解析成int和float但 JSON 語義上它們相等所以統(tǒng)一轉(zhuǎn)成float比較。bool單獨(dú)排除是因?yàn)門rue 1在 Python 里成立不排除的話true和1會(huì)被誤判為相等。數(shù)組順序按重要差異處理只要順序不同就報(bào)這是大多數(shù)接口聯(lián)調(diào)場(chǎng)景想要的。4.3 兩個(gè)實(shí)戰(zhàn)變體接口響應(yīng)比對(duì)與 json merge conflict 合并復(fù)查第一個(gè)變體是接口響應(yīng)比對(duì)。把兩個(gè)環(huán)境的接口響應(yīng)各自存成文件然后跑腳本curl -s https://api-a.example.com/v1/orders -H Authorization: Bearer $TOKEN resp-a.json curl -s https://api-b.example.com/v1/orders -H Authorization: Bearer $TOKEN resp-b.json python3 jsondiff.py resp-a.json resp-b.json腳本會(huì)輸出類似root[3].total: 12.5 ! 12.0的行直接定位到數(shù)組第 4 個(gè)元素的total字段差異。這比截兩張圖左右對(duì)比快十倍。注意接口響應(yīng)里如果有時(shí)間戳這種必然變化的字段比對(duì)前先過濾掉否則每次都有差異。第二個(gè)變體是多人協(xié)作時(shí)遇到的 json merge conflict。Git 合并兩個(gè)都改過package.json或配置文件的版本時(shí)會(huì)留下沖突標(biāo)記。手工解決沖突后拿這個(gè)腳本把我合出來的結(jié)果和本來的預(yù)期結(jié)果做一次比對(duì)能確認(rèn)合并過程中沒有意外丟掉字段。做法是把沖突解決后的文件存成merged.json把主干版本存成base.json然后python3 jsondiff.py base.json merged.json輸出全是missing in right就說明手動(dòng)合并時(shí)丟了內(nèi)容。這個(gè)場(chǎng)景里腳本的價(jià)值不是替代 Git 的 diff 工具而是給手工合并結(jié)果一個(gè)客觀的驗(yàn)收出口。我習(xí)慣在提交前跑一遍比反復(fù)肉眼確認(rèn)踏實(shí)得多。5. 格式化與比對(duì)的 5 個(gè)翻車現(xiàn)場(chǎng)與避坑清單5.1 現(xiàn)象JSON 中文被轉(zhuǎn)成 \uXXXX格式化之后 JSON 里的中文全變成\u4e2d\u6587這種形式。功能上沒錯(cuò)但沒法直接讀。原因某些格式化工具默認(rèn)啟用ASCII 安全輸出把非 ASCII 字符全部轉(zhuǎn)義。Python 的json.dumps默認(rèn)ensure_asciiTrue就是這個(gè)行為jq 在加了-a--ascii-output時(shí)也會(huì)這樣。另一個(gè)來源是復(fù)制到某些在線工具后它默認(rèn)按轉(zhuǎn)義模式輸出。解決用json.dumps(data, ensure_asciiFalse)寫腳本用 jq 時(shí)不加-a。如果你手里已經(jīng)有一個(gè)被轉(zhuǎn)義的文件用 jq 重新格式化一次會(huì)還原成中文因?yàn)?jq 解析時(shí)會(huì)自動(dòng)把\uXXXX解碼回字符。記住這條規(guī)則格式化工具轉(zhuǎn)義不轉(zhuǎn)義只是序列化配置不是數(shù)據(jù)損壞。5.2 現(xiàn)象XML 格式化后內(nèi)容全部擠在一行把 XML 丟給 xmllint 后發(fā)現(xiàn)輸出確實(shí)縮進(jìn)了但所有內(nèi)容還是在同一行上看起來跟沒格式化一樣。原因最常見的是你在命令后面又加了一層管道處理比如xmllint --format raw.xml | tr -d \n把 xmllint 辛苦加的換行全刪了。另一個(gè)隱蔽原因是文件里的換行是#10;實(shí)體轉(zhuǎn)義解析后是文本內(nèi)容里的真實(shí)換行不是排版換行這種文件格式化后內(nèi)容連在一起是正常的因?yàn)樗緛砭蜎]有標(biāo)簽間空白。解決別在管道里二次處理直接xmllint --format raw.xml --output pretty.xml寫文件看。如果文件本身沒有標(biāo)簽間空白你想讓它可讀只能先插入縮進(jìn)空白——但這時(shí)候修改的是文檔樹有可能影響依賴空白節(jié)點(diǎn)的下游邏輯。我的習(xí)慣是格式化只用于人眼排查不與簽名校驗(yàn)共用同一份輸出。排查完還是用原始文件干活。5.3 現(xiàn)象嵌套 JSON 比對(duì)誤報(bào)差異用自寫腳本比對(duì)時(shí)報(bào)出一堆root.a.b: 1 ! 1.0或者root.c: True ! 1這種差異但業(yè)務(wù)上明明是等價(jià)的。原因JSON 解析器對(duì)數(shù)字的處理不一致。Python 的json.loads把1解析成int把1.0解析成float而 JavaScript 的解析器一律變成number沒有類型區(qū)別。所以在 Python 里直接比較就會(huì)出現(xiàn)1 ! 1.0。True和1的坑更隱蔽因?yàn)閎ool是int的子類True 1成立反過來會(huì)讓你漏掉真正的類型差異。解決按前面腳本里的做法先排除 bool再把 int 和 float 統(tǒng)一成float比較。如果你不想改腳本就在生成比對(duì)文件時(shí)把兩邊都過一遍jq——jq 會(huì)把1和1.0在內(nèi)部統(tǒng)一處理輸出格式一致后再比。但注意 jq 這條路線對(duì) bool 不生效true和1在 jq 里仍然是不同類型。5.4 現(xiàn)象接口返回的 XML 帶 BOM 或多根節(jié)點(diǎn)xmllint 報(bào)錯(cuò)提示parser error或者Extra content at the end of the doc。文件看起來是合法 XML但就是過不了。原因從 Windows 環(huán)境拿到的 XML 文件開頭帶了一個(gè) UTF-8 BOMEF BB BFxmllint 在某些配置下會(huì)把 BOM 當(dāng)成內(nèi)容讀進(jìn)去導(dǎo)致解析失敗。另一個(gè)常見原因是工具或接口把多個(gè) XML 文檔拼在一個(gè)文件里返回了比如一個(gè)列表接口把每條記錄各吐了一個(gè) XML 根節(jié)點(diǎn)拼在一起就成了多根節(jié)點(diǎn)文檔。解決去 BOM 用一行sed -i 1s/^\xEF\xBB\xBF// file.xml。多根節(jié)點(diǎn)的情況如果文件是多個(gè) XML 文檔拼接可以用xmllint --recover --format file.xml嘗試恢復(fù)但更干凈的辦法是手工包一個(gè)外層根節(jié)點(diǎn)比如# 把多根節(jié)點(diǎn)包進(jìn)一個(gè) root 標(biāo)簽 { echo root; cat broken.xml; echo /root; } | xmllint --format -注意這只能用來排查包根節(jié)點(diǎn)后的文檔和原始語義不等價(jià)別拿它當(dāng)正式數(shù)據(jù)。至于xml 格式文件沒有標(biāo)簽怎么辦那種情況文件里根本沒有字符的那就不是 XML格式化工具無能為力先回頭查生成方。5.5 現(xiàn)象大文件格式化卡死幾十 MB 的 JSON 或 XML 丟給 jq、xmllint 后CPU 飆滿等了幾分鐘沒反應(yīng)最后被系統(tǒng) OOM 殺掉。原因這兩個(gè)工具都要把完整文檔讀入內(nèi)存再重建結(jié)構(gòu)JSON 的解析樹在內(nèi)存里通常是文件體積的幾倍到十幾倍。你本地格式化一個(gè) 200MB 的 json 數(shù)組內(nèi)存占用輕松到 2GB 以上。這不算工具 bug是結(jié)構(gòu)化數(shù)據(jù)的固有開銷。解決格式化前先看文件大小超過 50MB 就換個(gè)策略。JSON 用小樣本先驗(yàn)證格式或者用流式解析庫逐條讀XML 用xmllint --noout只校驗(yàn)不排版省掉序列化那部分開銷。在 spark 中讀取 json 的大文件場(chǎng)景正確做法是讓 spark 自己去解析不在本地先格式化——分布式引擎對(duì)大數(shù)據(jù)量的處理設(shè)計(jì)就是為這個(gè)場(chǎng)景存在的。記住格式化工具是給人眼看的不是給機(jī)器跑批的。6. 把三件套接進(jìn)日常調(diào)試一個(gè) 20 行的入口腳本前面講了原理和命令最后把這些落成一個(gè)我每天都在用的入口腳本。它做的事情只有三件校驗(yàn)、格式化、比對(duì)全部基于退出碼判斷結(jié)果能直接粘到~/.bashrc或~/.zshrc里# JSON 校驗(yàn)成功輸出 ok失敗輸出錯(cuò)誤并返回 1 jsoncheck() { jq -e . $1 /dev/null echo json ok || echo json broken; } # XML 校驗(yàn)只解析不輸出 xmlcheck() { xmllint --noout $1 echo xml ok || echo xml broken; } # JSON 比對(duì)調(diào)用前面的 jsondiff.py有差異打印路徑并返回 1 jsondiff() { python3 $HOME/bin/jsondiff.py $1 $2; } # 一鍵格式化輸出到 .pretty 文件不動(dòng)原文件 jsonpretty() { jq -S . $1 $1.pretty; } xmlpretty() { xmllint --format $1 --output $1.pretty; }用法就是jsoncheck resp.json、jsondiff left.json right.json有差異時(shí)腳本把路徑一行行列出來退出碼可以接進(jìn) CI 或者 pre-commit 鉤子。我還習(xí)慣在比對(duì)前先做一次jq -S預(yù)處理讓鍵序一致這樣即使萬一用到diff也不會(huì)被排序差異干擾。驗(yàn)證方式很簡(jiǎn)單自己造兩個(gè)只差一個(gè)字段值的 JSON 文件跑一遍腳本確認(rèn)輸出路徑準(zhǔn)確再把它們排序后跑一遍確認(rèn)退出碼是 0。這套東西陪我處理過很多次看起來一樣、實(shí)際不一樣的接口問題?,F(xiàn)在我的習(xí)慣是任何一次接口聯(lián)調(diào)拿到響應(yīng)先格式化結(jié)構(gòu)看清了再比對(duì)比對(duì)出差異先看路徑路徑指向的字段往往比我想象的更靠?jī)?nèi)層。格式化工具最大的價(jià)值不是排版好看而是讓差異無處可藏。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取