處理大型 JSON 檔案
當 JSON 大到無法整批塞進記憶體時的策略:串流解析器、NDJSON、分塊、壓縮,以及不會卡住的工具。
5 MB 的 JSON 沒問題。500 MB 的 JSON 若用處理 5 MB 的方式對待,會把
你的編輯器、JSON.parse 與記憶體預算一起搞掛。本文是大型 JSON 的
求生包:為什麼它慢、該選哪種解析器、何時該換格式,以及哪些工具能
撐住 10 萬筆紀錄的陣列。
為什麼大 JSON 慢
每個主流語言的預設解析器——JSON.parse、json.loads、
json.Unmarshal——都是 整份文件、整批載入記憶體。它把整個檔案
讀成字串,再配置一份對應的物件圖。兩項成本會隨檔案大小增長:
- 記憶體——解析後的結構通常是檔案大小的 3–5 倍(物件 header、hash bucket、boxed number)。500 MB 的檔案常常變成 2 GB heap。
- 延遲——解析是單一流程、大致序列。500 MB 的 JSON 在效能不錯的硬體上仍需數秒,期間解析執行緒不可用。
幾 MB 以下用整批解析器是對的;超過就需要不同策略。
串流解析器
串流解析器在走訪輸入時 發出事件,而非建構樹。經典介面是 SAX 風格:
onStartObject、onKey、onValue、onEndArray。你看到每筆紀錄就決定
要不要留。
函式庫:
- JavaScript——
clarinet、stream-json、JSONStream。 - Python——
ijson。 - Go——
encoding/json的Decoder配Token()。 - Rust——
serde_json::Deserializer::from_reader。
以 stream-json 從巨型頂層陣列抽出紀錄,全程不持有整份文件:
import fs from "node:fs";
import { parser } from "stream-json";
import { streamArray } from "stream-json/streamers/StreamArray.js";
fs.createReadStream("orders.json")
.pipe(parser())
.pipe(streamArray())
.on("data", ({ value }) => {
// value 是一筆紀錄,處理後丟掉
if (value.total > 100) console.log(value.id);
})
.on("end", () => console.log("done"));
不論檔案多大,記憶體都是平的。
NDJSON / 行分隔 JSON
最簡單的「串流友善」格式是 每行一個 JSON 值:
{"id":"1","total":42}
{"id":"2","total":85}
{"id":"3","total":17}
這就是 NDJSON(或 JSON Lines、.ndjson、.jsonl)。所有雲端
日誌都這樣寫,所有分析平台都這樣吃。解析就是每行一次 JSON.parse,
易於串流、易於拆給 worker、shell 也能 grep。
若你能控制大資料的產出端,請改用 NDJSON 而非巨型陣列。下游會 感謝你。
import readline from "node:readline";
import fs from "node:fs";
const rl = readline.createInterface({ input: fs.createReadStream("orders.jsonl") });
for await (const line of rl) {
if (!line.trim()) continue;
const record = JSON.parse(line);
// ...
}
分塊與分頁
若產出端堅持發出單份巨型文件,兩種模式可救:
- 伺服端分頁——
?page=1&pageSize=1000cursor。每頁是小文件。記得帶nextcursor 或 total 讓客戶端知道何時停。 - 客戶端分塊——從磁碟讀時使用串流解析器,每處理 N 筆才寫進資料庫/網路/輸出。
要隨手探索巨大文件,/zh-Hant/json/viewer 採用 延遲展開子樹,不會把整張圖物化。
線上壓縮
JSON 壓縮率很高——gzip 通常省 80–90%,Brotli 與 Zstandard 更好。 重複的鍵與空白正是壓縮演算法最擅長的模式。
對外提供大型 JSON 的 HTTP endpoint:
- 設定
Content-Encoding: gzip(或br、zstd),於來源端預壓或即時壓縮。 - 把靜態大型 JSON 在 build 階段預壓,直接提供
.json.gz、.json.br。 - 不必為了壓縮先 minify——壓縮會把空白省回來。
更多權衡見 壓縮與美化 JSON。
不會卡住的工具
多數編輯器與 JSON viewer 為了語法高亮會把整份文件解析成 DOM 樣結構, 這就是它們在大檔案上會卡住的原因。較好的策略:
- JSON Viewer——延遲展開樹;子樹按需解析。
- JSON Formatter——超過 ~1.5 MB 改用 Web Worker,UI 不會卡。
- 終端機用
jq -c逐行處理。 - 用
less做原始檢視——絕不要用 Vim 開 500 MB。
什麼時候該離開 JSON
JSON 是文字。文字冗長。對於吞吐量或成本敏感的工作流,二進位格式是 正解:
- Protocol Buffers / FlatBuffers——schema 驅動、很快、很小。
- MessagePack——資料模型像 JSON、二進位編碼,多數 JSON 程式碼可直接替換。
- Avro / Parquet——欄狀,分析工作的最佳解。
- CBOR——IETF 的二進位 JSON,用於 IoT、WebAuthn。
若你常在數百萬筆紀錄上跑相同的聚合查詢,請改用 Parquet。
實務流程
「我拿到一份 2 GB 的 JSON」的固定流程:
- 用
head看前 1000 行確認形狀(單一文件 vs NDJSON)。 - 若是 NDJSON,串流即可。
- 若是紀錄陣列,使用串流解析器框住該陣列。
- 若是深度巢狀文件,用 JSONPath 抽出感興趣的部分再串流。
- 要把結果交付他人,展平成 CSV——見 JSON 轉 CSV:攤平巢狀資料。
延伸閱讀
- JSONPath 詳解——抽出你真正需要的子集。
- JSON 轉 CSV:攤平巢狀資料——以扁平格式輕鬆吞數百萬筆。
- /zh-Hant/json/viewer——探索大文件而不讓瀏覽器融化。