← 全部文章

在 JavaScript 中如何處理損壞的 JSON

現實中的 JSON 常常是髒的:尾隨逗號、單引號、Python 字面量、markdown 程式碼柵欄。學習常見模式、如何寫一個安全解析輔助函式,以及何時該用專門的修復函式庫。

每個 JavaScript 開發者都看過這一幕:JSON.parse() 丟出一個 SyntaxError,應用程式就崩了。那段 JSON 來自一個 API、設定檔,或使用者剪貼簿 —— 就是解析不過去。本指南會逐步說明為什麼、實際情境裡壞 JSON 長什麼樣,以及如何優雅地處理 —— 從一個簡單的 try/catch 到自動修復。

1. JSON.parse() 為什麼會失敗

JSON 標準(RFC 8259)刻意嚴格。JSON.parse() 嚴格依規範實作 —— 任何偏離,不論多小,都會以一個沒有部分恢復的 SyntaxError 丟出:

try {
  const data = JSON.parse('{ name: "Ada" }'); // 未加引號的 key
} catch (err) {
  console.error(err.message);
  // SyntaxError: Expected property name or '}' in JSON at position 2
}

和瀏覽器解析 HTML(內建錯誤恢復模型)不同,JSON parser 是一道硬閘。嚴格解析是個特性:它逼生產方輸出乾淨資料。但當你無法控制資料來源時 —— LLM 輸出、手工編輯的設定檔、第三方 API —— 你就需要一套策略來應對這道閘關上的時候。

2. 最常見的壞 JSON 模式

在伸手抓修復工具之前,先認清你面對的是什麼。實務上幾乎所有壞 JSON 都落在以下某類:

單引號代替雙引號

// ❌ 非法 JSON
{ 'name': 'Ada Lovelace' }

// ✅ 合法
{ "name": "Ada Lovelace" }

JavaScript 物件字面量接受單引號;JSON 不接受。開發者把一段物件字面量直接複製到 JSON 環境,就會反覆撞上這個錯誤。

尾隨逗號

// ❌ 非法 JSON
{
  "name": "Ada",
  "active": true,   // 尾隨逗號
}

// ✅ 合法
{
  "name": "Ada",
  "active": true
}

現代 JavaScript 允許陣列與物件中的尾隨逗號。JSON 不允許。這是手工編輯 JSON 檔案中最常見的單一錯誤。

未加引號的物件 key

// ❌ 非法 JSON
{ name: "Ada", score: 98 }

// ✅ 合法
{ "name": "Ada", "score": 98 }

JavaScript 物件字面量不要求 key 加引號。JSON 要求所有 key 為雙引號字串,無例外。

JavaScript 註解

// ❌ 非法 JSON
{
  // 這是使用者紀錄
  "name": "Ada",
  /* 2024 新增 */
  "active": true
}

JSON 沒有註解語法。註解有時會加到設定檔(JSONC —— 含註解的 JSON —— 是個流行擴展),但 JSON.parse() 會拒絕它們。

多行字串

// ❌ 非法 JSON
{
  "description": "line one
  line two"
}

// ✅ 合法 —— 轉義換行
{
  "description": "line one\nline two"
}

JSON 的字串值必須在單一行。字串內不允許字面換行;改用 \n 轉義序列。

NaN、Infinity 與 undefined

// ❌ 不是合法 JSON
{ "ratio": NaN, "limit": Infinity, "value": undefined }

// ✅ 合法替代
{ "ratio": null, "limit": 1e308, "value": null }

它們是合法的 JavaScript 值,但不屬於 JSON 規範。JSON.stringify() 會悄悄轉換(NaNInfinity 變成 nullundefined 屬性整個被丟掉),可能造成隱晦的往返 bug。

Python 字面量

// ❌ Python 風格 —— 非法 JSON
{ "active": True, "deleted": False, "value": None }

// ✅ 合法 JSON
{ "active": true, "deleted": false, "value": null }

JSON 一開始就是為跨語言互通設計的。Python 那些首字母大寫的 TrueFalseNone 是跨語言資料 bug 的常見來源。

3.「修復」與「解析」不是同一件事

務必分清兩種根本不同的操作:

  • 嚴格解析 —— 驗證輸入完全是正確的 JSON、精準回報任何錯誤位置、拒絕任何偏離。當你能控制生產方並想強制契約時用。
  • 修復解析 —— 用一組啟發式規則嘗試從語法不完美的輸入恢復出一個合法 JSON 值。當資料來自不可信或不精確的來源(LLM 輸出、使用者剪貼簿、舊服務)時用。

修復 parser 會做假設。遇到 True 它假設你想的是 true。遇到尾隨逗號就把它移除。對上面那些模式這些假設幾乎都正確 —— 但也可能默默掩蓋一份真正畸形的文件,讓「修復」改了你本來的意思。

正確的工具取決於情境。在 schema 驗證的 API 回應管線中,你想要嚴格解析,讓壞資料立刻浮上水面。在使用者貼一段 Slack 訊息裡複製來的 JSON 的開發者工具中,修復解析能省時。

值得認識的函式庫

兩個 npm 套件分擔了大部分重活,各有互補的取捨:

  • jsonrepair —— 一個寬容的修復 parser。把壞 JSON 餵給它,得到一段盡力修好的合法字串。能處理上述模式(單引號、尾隨逗號、無引號 key、註解、Python 字面量、markdown 圍欄、未閉合括號、不完整輸入)。同步、零相依、體積小 —— 記憶體中有完整文件時最理想。
  • clarinet —— SAX 風格的串流 JSON parser。當 JSON 分塊到達(資料流、大檔、LLM 逐 token 回應)且你想 key/value 一出現就反應,而非等完整文件,就用它。它對語法很嚴格,若來源不可靠,請搭配 jsonrepair

經驗法則:手上是一小段不可信 blob 就 修復;文件很大或漸進到達就 串流處理

4. 捕捉錯誤並給使用者友善回饋

最低限度的可用模式是一個 try/catch。一律這麼做 —— 別讓 JSON.parse() 抛出未被捕捉的錯誤:

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

const result = safeParseJson(userInput);
if (!result.ok) {
  showError(`Could not parse JSON: ${result.error}`);
}

JSON.parse() 的錯誤訊息因 JavaScript 引擎而異。V8(Node.js、Chrome)會附帶位置提示:

// V8 錯誤訊息
"Expected ',' or '}' after property value in JSON at position 42"

// Firefox SpiderMonkey
"JSON.parse: expected ',' or '}' after property value in object
at line 3 column 5 of the JSON data"

位置資訊有用但綁特定引擎。若你需要跨環境可靠的行/欄回報,自寫一個遞迴下降 parser 能產出結構化錯誤,對使用者更友善。

5. 何時自動修復 vs. 請使用者確認

並非所有修復風險都相同。以下是一個實用啟發法:

  • 可安全自動修復 —— 尾隨逗號、空白正規化、引號風格轉換、無引號 key、JavaScript 註解、Python 的 boolean/null 字面量。這些都是無語意歧義的機械轉換。
  • 考慮詢問使用者 —— 結構性修復,例如自動閉合未閉合的物件或陣列({"name": "Ada"{"name": "Ada"}),或剝除看似有意義的內容(移除看起來刻意寫的註解)。修復多半正確,但使用者可能想確認。
  • 永遠標示、絕不默默修 —— 像 NaN → null 這種型別轉換會改變資料值。要讓使用者看到改了什麼。

好的修復介面會顯示 diff:左側原始輸入、右側修復輸出。使用者在複製或提交結果前可確認修復正確。

6. 在瀏覽器處理 JSON 的隱私優勢

當你在瀏覽器中本機處理 JSON —— 使用已在此頁執行的 JavaScript 引擎 —— 資料從未離開使用者的電腦。這比開發者通常意識到的更重要:

  • API key 與憑證 —— 開發者經常貼含機密的 JSON payload。伺服端工具會把每個請求記下來。
  • PII 與健康資料 —— 沒有資料傳輸時,GDPR 與 HIPAA 合規會簡單得多。
  • 公司內部資料 —— 許多安全政策禁止把內部資料貼到第三方 web 服務。

瀏覽器內處理帶來零風險的 JSON 修復:沒有伺服器、沒有日誌、沒有資料留存。運算在一個 <textarea> 與幾 KB 的 JavaScript 內進行。

常見問題

如何在 JavaScript 中處理 JSON.parse 錯誤?

把呼叫包進 try/catch,回傳結構化結果,而不是讓 SyntaxError 往上傳(見上方 safeParseJson helper)。對不可信輸入絕不要不加 guard 地呼叫 JSON.parse()

有辦法自動解析壞 JSON 嗎?

有 —— 修復 parser 透過套用啟發式規則(移除尾隨逗號、把單引號轉雙引號、把 True 轉小寫)從不完美輸入恢復出合法值。用於不可信來源(LLM 輸出、貼上文字);勿用於有契約驗證的 API 回應,那裡你想讓壞資料浮出。

我何時該修復 JSON vs. 拒絕它?

人貼了一段大致是 JSON 的東西或 LLM 產出時就修復;當你能控制生產方並想強制 schema 時就拒絕(嚴格解析)。像 NaN → null 這類型別轉換永遠要標示,絕不默默套用。

壞 JSON 最常見的成因是什麼?

把 JavaScript 物件字面量當成 JSON —— 單引號、無引號 key、尾隨逗號。見 JSON vs JavaScript 物件[object Object] 與其他錯誤 中的語法錯誤參考。

7. 立刻試試 —— 把你的壞 JSON 貼進去

若你手上有畸形的 JSON 字串需要立刻修,JSON Fix 能處理上述所有模式 —— 單引號、尾隨逗號、無引號 key、Python 字面量、註解、markdown 程式碼圍欄等等。一切都在你的瀏覽器中執行,不會送往任何伺服器。