JSON 工具箱

JSON 報錯資訊反查表:從報錯原文直接定位改法

結論:按 JSON.parse 報錯原文反查原因與改法:12 條高頻報錯對應 Chrome、Firefox、Safari 三種措辭,涵蓋 HTML 錯誤頁、截斷輸入、重複解析、NDJSON 拼接等線上場景,並解釋報錯位置為什麼常指向「下游」。

實戰約 8 分鐘發佈於 2026-10-30更新於 2026-11-04
JSON 報錯資訊反查表封面

結論:看到 JSON.parse 報錯先抓位置和 token 兩個事實,位置是 0 就查 HTML 回應、BOM 與傳錯型別,報 Expected 就看前一個字元,不要從報錯位置開始逐行重讀。

報錯原文不是雜訊,是答案:解析器告訴你它在哪個字元、期待什麼、實際收到什麼。把報錯當檢索詞去查,比把整段 JSON 重寫一遍快得多。

JSON.parse() 的報錯資訊由三個部分組成:位置(position 或 line/column)、期待(Expected...)、實際收到(Unexpected token X 或 xxx is not valid JSON)。三個引擎措辭不同——Chrome 和 Node(V8)報 Expected ',' or '}' after property value in JSON at position 25,Firefox 報 JSON.parse: expected ',' or '}' after property value in object at line 3 column 1,Safari 報 JSON Parse error: Expected ',' or '}'——但指的都是同一處語法斷點。所以查報錯時別管措辭差異,先抓三個事實:哪個字元、期待什麼、收到什麼。

一、12 條高頻報錯反查表

下表左邊一列是你在主控台裡看到的最有辨識度的片段(以 V8 措辭為準),右邊是真實原因和改法。Firefox/Safari 的措辭差異放在括號裡。

報錯原文(V8) 真實原因 怎麼改
Unexpected token < in JSON at position 0 介面回傳的是 HTML(404 頁、500 錯誤頁、登入跳轉、代理兜底頁),不是 JSON 先看 response.status 與 Content-Type,別去修 JSON
Unexpected end of JSON input 輸入為空或被截斷:空回應體、204、下載中斷、複製漏了最後一行 先判空再解析,比對 Content-Length 找截斷點
Unexpected token o / "[object Object]" is not valid JSON 把已解析的物件又傳給 JSON.parse(),物件被強轉成字串 typeof 檢查,已是物件就直接用
"undefined" is not valid JSON 變數沒賦值(JSON.parse(undefined)) 初始化兜底值,解析前判空
Expected double-quoted property name in JSON 尾逗號({"a": 1,}),解析器讀到逗號後期待下一個鍵卻遇到 } 刪除最後一項後面的逗號
Expected property name or '}' in JSON 鍵沒加引號或用了單引號({name: "Ada"}、{'name': 'Ada'}) 所有鍵補雙引號
Expected ',' or '}' after property value in JSON 缺逗號({"a": 1 "b": 2})或值後面出現了註解 補逗號;刪掉註解
Unexpected token '/' JSON 裡寫了 // 或 /* */ 註解 刪註解,或設定檔改用 JSONC 解析器
Bad control character in string literal 字串裡有裸換行、裸 Tab 或其他 U+0000–U+001F 控制字元 換行寫成 \n,改用 JSON.stringify 產生
Unterminated string in JSON 字串開了引號沒閉合,或字串內部的雙引號沒轉義 內部引號寫 \"
Unexpected non-whitespace character after JSON 一段文字裡有兩個文件:{}{}、NDJSON、JSONP 包裹 按行拆開逐行解析,或用 NDJSON 解析器
JSON.parse is not a function 自己宣告的變數把全域 JSON 遮蔽了 重新命名變數,別佔用 JSON

前兩條值得單獨展開,因為它們都不是「JSON 寫錯了」,而是「你拿到的根本不是你以為的東西」。

二、position 0 的三種常見來源

報錯位置是 0,說明第一個字元就非法。你看不到可疑字元,不代表字元不存在——大概率是這三種之一:

第一種:<,回傳了 HTML。 這是線上環境最高頻的解析報錯。介面路徑寫錯、登入態過期被閘道重定向、後端異常渲染了錯誤範本,回應體以 <!DOCTYPE html> 開頭,解析器第一個字元就放棄了。正確動作是打開 Network 面板看原始回應體,把介面問題解決掉,而不是反覆檢查自己的 JSON 語法。

第二種:BOM 或零寬字元。 Windows 記事本儲存的檔案常帶 UTF-8 BOM(\uFEFF),從網頁或聊天工具複製的文字可能帶零寬空格(\u200B–\u200D)。這些字元在編輯器裡不可見,解析器卻看得一清二楚。處理辦法:

// 去掉 BOM 和零寬字元後再解析
const cleaned = raw.replace(/^\uFEFF/, '').replace(/[\u200B-\u200D]/g, '');
const data = JSON.parse(cleaned);

第三種:傳錯了型別。 JSON.parse(undefined) 報 "undefined" is not valid JSON,JSON.parse(someObject) 報 Unexpected token o(物件被強轉成 "[object Object]" 後,o 在 position 1 非法)。這兩種都是「資料已經解析過了」或「根本沒拿到資料」,修法是檢查型別而不是檢查語法:

let data = text;
if (typeof data === 'string') {
  data = JSON.parse(data); // 只在確實是字串時才解析
}

三、報錯位置在下游,真實原因在上游

兩類報錯的位置資訊會「騙人」,這也是反查表裡最容易被誤導的兩種情況:

  • 缺逗號:{"a": 1 "b": 2} 報 Expected ',' or '}' after property value,位置指向 "b" 那一行——報錯沒錯,但容易被理解成 "b" 有問題。實際要改的是上一行末尾。
  • 缺閉合括號:深層巢狀少了 } 或 ],解析器會一路吞到輸入末尾才報 Unexpected end of JSON input。位置在檔案末尾,真正缺括號的地方在報錯位置之前很遠處。往回找最近一個未閉合的 { 或 [,配對計數比目測快。

處理這類問題的通用手法是二分:把報錯位置之前的文字截出來單獨解析,能過就說明問題在報錯位置附近;不能過就繼續往前二分,第一段解析失敗的邊界就是真實出錯點。大檔案不建議肉眼數括號,先格式化,層級對齊之後缺哪一層一眼可見(可以用站內的 JSON 格式化 先整理再查)。

四、解析成功但結果不對的四種「假健康」

比報錯更隱蔽的是不報錯但資料不對。這四種情況 JSON 合法、解析成功,問題出在資料本身的形態:

  1. 雙重編碼:整個文件被 JSON.stringify 了兩次,解析第一層得到的是字串而不是物件。typeof result === 'string' 就再 parse 一次,但根治方法是找到重複序列化的源頭。
  2. 重複鍵靜默覆蓋:{"name": "Tom", "name": "Jerry"} 是合法 JSON,多數解析器不報錯,後者覆蓋前者。資料從多個來源合併時最容易踩,用 JSON 驗證 工具或嚴格的解析選項提前暴露。
  3. NDJSON 被當單個文件:串流介面每行一個物件,直接 parse 整段文字報 Unexpected non-whitespace character after JSON。按 \n 切分逐行解析。
  4. 數字精度悄悄丟失:超過 2^53 - 1 的整數(如雪花 ID)解析後末位變成 0,不報任何錯。ID 一律用字串傳輸。

這四種情況的共同點是:解析器認為一切正常。所以介面資料異常時,第一步是列印原始文字的前 80 個字元和 typeof,確認形態,再談內容。

五、寫程式碼時就讓報錯失去機會

反查表是事後手段,預防是事前手段。四個習慣能讓大部分報錯在發生前就被攔截:

// 1. 解析回應前先做兩道檢查
const response = await fetch('/api/users');
if (!response.ok) {
  throw new Error(`HTTP ${response.status},介面沒有回傳資料`);
}
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
  throw new Error(`期望 JSON,實際是 ${contentType}`);
}
const data = await response.json(); // 2. json() 內部已處理解析,別再 parse 一次
  • 產生用序列化,不用拼接:JSON.stringify() 會正確處理引號、換行、控制字元;手工拼字串是 Bad control character 和 Unterminated string 的最大來源。
  • try/catch 必須有兜底:JSON.parse 一拋例外就是同步中斷,外層不接住整個流程就掛。解析 localStorage、讀取外部檔案這類輸入不可信的場景,catch 裡要有預設值或明確的錯誤上報。
  • 結構驗證交給 Schema:語法合法不等於結構正確,欄位缺失、型別不符要靠 JSON Schema 在邊界處攔截,相關取捨見 JSON Schema 能解決什麼問題。

六、快速決策路徑

最後把全文壓縮成一個決策順序:看到報錯先抓位置和 token——

  1. 位置是 0 → 查 <(HTML)、BOM、傳錯型別三種來源;
  2. 報 end of input → 查空回應和截斷,往回找未閉合括號;
  3. 報 Expected ... → 看前一個字元,八成是缺逗號或缺引號;
  4. 報 after JSON data → 是多文件拼接,按行拆;
  5. 解析成功但資料怪 → 走「假健康」四項檢查。

配合逐條排查的完整流程(見 JSON 解析報錯怎麼修),90% 的解析問題可以在五分鐘內定位。報錯原文本身值得收藏一份——它是解析器寫給你的除錯日誌,措辭三年不變,查一次管一年。

JSON 報錯 Unexpected token 是什麼意思?

解析器在某個位置遇到了不符合 JSON 語法的字元。token 就是被拒收的那個字元:< 通常是介面回傳了 HTML,' 是用了單引號,/ 是寫了註解,普通字母常見於未加引號的鍵名或 undefined、NaN 這類 JavaScript 專屬字面量。先看 token 是什麼,再看位置,就能把方向收窄到一種原因。

Unexpected end of JSON input 怎麼修?

兩種來源:輸入為空(空回應體、localStorage 裡存了空字串)或輸入被截斷(下載中斷、複製漏行、長度限制)。先 if (!text) return 處理空輸入;再確認文字完整,截斷的往回找最近一個未閉合的 {、[ 或 " 補上。注意這個報錯也可能因為深層缺括號,位置只是恰好落在末尾。

為什麼 Chrome、Firefox、Safari 的報錯不一樣?

三個瀏覽器用不同的 JS 引擎(V8、SpiderMonkey、JavaScriptCore),報錯措辭各自實作,MDN 收錄了全部官方措辭清單。但三者的報錯都包含同樣的三要素——位置、期待、實際收到——語義一一對應,按本表反查時先轉換成 V8 措辭即可。

報錯位置指向的地方看起來沒問題怎麼辦?

說明真實錯誤在報錯位置之前。典型是缺逗號(報錯在下一行)和缺閉合括號(報錯在檔案末尾)。從報錯位置往回逐字元看前一個 token,或者用二分法截取前半段單獨解析,第一段解析失敗的邊界就是真實出錯點。