FormatArc JSON 格式化器的執行結果畫面FormatArc JSON 格式化器的執行結果畫面
作者: FormatArc 編輯部發布日期: 2026-09-02更新日期: 2026-09-02

JSON 解析錯誤的原因與修法|5 個常見錯誤一次解決

處理 JSON 資料時,你幾乎一定會遇到 SyntaxError: Unexpected token 這個錯誤訊息。處理 API 回應時、讀取設定檔案時,突然跳出這個錯誤,整個流程就卡住了。

想快速找到錯誤位置的話,把 JSON 貼上 JSON 格式化器 就能看到錯誤所在行號與原因。

原因只要知道,修正其實很簡單。但光看錯誤訊息,很多時候很難判斷問題出在哪裡。這篇文章說明 JSON 解析錯誤的讀法,以及 5 個最常見的原因,都附帶具體範例。

怎麼讀 SyntaxError: Unexpected token

在瀏覽器或 Node.js 中解析 JSON 時,如果語法有問題就會拋出錯誤。以下是一個壞掉的 JSON(物件的值用了單引號):

{
  "name": "Alice",
  "age": 30,
  "city": 'Tokyo'
}

現行 V8(Chrome 與 Node.js 使用的引擎)會回報:

Unexpected token ''', ..."  "city": 'Tokyo'
}" is not valid JSON

這個訊息包含兩項資訊:

  • Unexpected token ''' — 解析器遇到了它不預期的字符 '(單引號)
  • ..." "city": 'Tokyo' 之後的部分 — 錯誤位置前後截取的文字片段

舊版引擎會回報 Unexpected token ' in JSON at position 14 這樣的 position 數字(從開頭數第幾個字符),但現行訊息改為引用錯誤周圍的文字。兩種寫法都在後面對照表中整理。不管是哪種格式,換行與空白都會計入,手動數字符在大型 JSON 檔案中不切實際。使用 JSON 格式化器 就能立刻定位。

5 個最常見的原因

1. 行尾多逗號(trailing comma)

JavaScript 的陣列或物件末尾加逗號也不會報錯。但 JSON 規格不允許行尾多逗號。

{
  "name": "Alice",
  "age": 30,
  "city": "Tokyo",
}

要刪除 "Tokyo" 後面的那個逗號。

{
  "name": "Alice",
  "age": 30,
  "city": "Tokyo"
}

"Tokyo" 後面的逗號就是原因。現行 V8 會回報:

Expected double-quoted property name in JSON at position 53 (line 5 column 1)

解析器讀完逗號後期待下一個鍵值對,但實際來的是閉括號,所以回報「期待雙引號包裹的屬性名稱」。這跟陣列的行尾多逗號行為不同。

陣列中也會出現同樣的錯誤,但回報方式不一樣:

{
  "colors": ["red", "green", "blue",]
}

現行 V8 對這個案例回報:

Unexpected token ']', ..."", "blue",]
}" is not valid JSON

陣列行尾多逗號後面緊接的字符是 ],所以 V8 把它當作 unexpected token 回報。把 "blue" 後面的逗號刪掉就能解析。如果你的編輯器格式化器有自動補逗號的設定,JSON 檔案建議關掉。

2. 單引號

Python 字典或 JavaScript 物件可以用 ',但 JSON 只接受雙引號 "

{'name': 'Alice'}

這不是合法的 JSON。全部要換成雙引號。

{"name": "Alice"}

Python 中把字典轉成 JSON 字串時,請用 json.dumps() 而不是 str()str() 會輸出單引號。

3. 鍵沒加引號

JavaScript 中物件的鍵如果是合法識別碼就不需要引號。JSON 中,鍵必須用雙引號包起來。

{name: "Alice"}

正確寫法:

{"name": "Alice"}

4. 註解

設定檔案中很常犯的錯誤。JSON 規格沒有註解語法

{
  // 使用者名稱
  "name": "Alice"
}

///* */ 都必須移除。另外 tsconfig.json 等部分檔案使用 JSONC(JSON with Comments)擴展規格,允許註解存在,但標準 JSON 解析器無法讀取。

如果你希望設定檔案保留註解,可以考慮用 YAML 管理。YAML 以 # 原生支援註解,兩種格式需要時可以互轉。

5. BOM(Byte Order Mark)

UTF-8 檔案開頭如果有 BOM(\uFEFF),解析器會把它當作非法字符。現行 V8 回報:

Unexpected token '', "{
  "name"... is not valid JSON

第一個 ' 後面那個看起來什麼都沒有的字符就是 BOM。舊版引擎回報的是 Unexpected token in JSON at position 0。移除 BOM 有三種方法:

  • VS Code:點狀態列的編碼顯示,選「以編碼儲存」,選不含 BOM 的「UTF-8」
  • 命令列:sed -i '1s/^\xEF\xBB\xBF//' file.json
  • 十六進位編輯器:刪除檔案開頭 3 個位元組(UTF-8 BOM 的 EF BB BF

如果無法控制檔案的儲存方式(例如讀取使用者上傳的檔案),就在程式端移除開頭的 BOM:

const cleaned = text.replace(/^\uFEFF/, '');
const data = JSON.parse(cleaned);

其他原因

除上述 5 個典型原因外,以下情況也會導致解析錯誤:

括號不配對

缺少閉括號 }] 時,多數情況下錯誤在檔案末尾才回報。嵌套很深的 JSON 用肉眼找不配對的括號非常困難。使用能顯示括號配對的格式化器可以節省大量時間。

字串中的控制字符

字串內如果有未轉義的換行或 tab,就會出錯。換行要用 \n 來轉義。

{"message": "Hello
World"}

正確寫法:

{"message": "Hello\nWorld"}

開頭多零的數字

JSON 數字不能以零開頭。007 是非法值,要改成 7 這樣的標準數字寫法。

undefined 與 NaN

JavaScript 的 undefinedNaN 不能作為 JSON 值。含有 undefined 的物件序列化時,多數序列化器會跳過那個鍵或拋出錯誤。NaNInfinity 同樣會被拒絕。

舊版與現行錯誤訊息對照表

如果你對照的是舊版指南、快取在 Stack Overflow 的回答、或這篇文章較早的版本,發現用語跟自己的主控台對不上,原因就是:JSON.parse() 的錯誤訊息隨 JavaScript 引擎演進而改變。本文其他部分主要以現行用語為主,但也保留舊用語方便搜尋。

主要變化有兩點。第一,帶引號的 token 格式(Unexpected token 'X', "..." is not valid JSON)不再回報 position 數字,改為引用錯誤周圍的文字。第二,Expected ... in JSON at position N 格式雖然保留 position,但現行多了 (line L column C),舊版沒有這個資訊。

本文對應位置舊版訊息現行 V8 輸出(實測)
開頭範例(單引號)Unexpected token ' in JSON at position 14Unexpected token ''', ..." "city": 'Tokyo'
}" is not valid JSON
原因 1:物件行尾多逗號本文未引用(僅說明「閉括號成為 unexpected token」)Expected double-quoted property name in JSON at position 53 (line 5 column 1)
原因 1:陣列行尾多逗號本文未引用(與物件相同說明)Unexpected token ']', ..."", "blue",]
}" is not valid JSON
原因 3:鍵沒加引號本文未引用Expected property name or '}' in JSON at position 4 (line 2 column 3)
原因 4:註解本文未引用Expected property name or '}' in JSON at position 4 (line 2 column 3)
原因 5:BOMUnexpected token in JSON at position 0Unexpected token '', "{
"name"... is not valid JSON
JSON.parse(undefined)Unexpected token u in JSON at position 0"undefined" is not valid JSON
JSON.parse({ name: "Alice" })Unexpected token o in JSON at position 1"[object Object]" is not valid JSON
HTML 回應(下方環境對照表)Unexpected token < in JSON at position 0Unexpected token '<', "<!DOCTYPE html>" is not valid JSON

「現行 V8 輸出」欄的字串,是拿本文中使用的 JSON 範例直接丟給 JSON.parse() 實測的結果(Node v26.3.1 / V8 14.6)。執行腳本與輸出結果已提交在 scripts/benchmarks/json-parse-error-messages/,每一行都可以復現。至於哪個 V8 版本開始改變用語,尚未驗證,本表確認的只是「現行 V8 目前回傳這些訊息」這個事實。

依症狀判斷:token u / token o / end of input

錯誤訊息中出現的字符本身,就能大幅縮小原因範圍。以下三種變體都不是 JSON 檔案內容的問題,而是「交給解析器之前」就出了狀況。另外,如果 token 是 <,代表回傳的是 HTML 而非 JSON(參見下方環境對照表)。

Unexpected token u in JSON at position 0

JSON.parse 收到的是字串 "undefined"u 是它的第一個字,舊版引擎因此在錯誤訊息中以 u 標識。這幾乎可以確定你傳進去的值在這個時點就是 undefined

const raw = localStorage.getItem("settings"); // 鍵不存在則為 null
JSON.parse(undefined); // 拋出的錯誤: "undefined" is not valid JSON

現行 V8 不再只點名一個字符,而是引用整個轉換後的字串("undefined" is not valid JSON)。解析前先確認值是否真的存在。API 回應正文為空、儲存鍵不存在、變數名打錯字,都是典型原因。

Unexpected token o in JSON at position 1

"[object Object]" 這個字串被丟給解析器時產生的錯誤。position 0 的 [ 看起來像陣列開頭,所以舊版引擎回報在 position 1 的 o 失敗。當把 JavaScript 物件本身(而非 JSON 字串)傳進 JSON.parse,隱式的字串轉換就會產生這個形態。

const data = { name: "Alice" };
JSON.parse(data); // data 被轉為 "[object Object]"。拋出的錯誤: "[object Object]" is not valid JSON

那個值已經是解析完成的状态。直接用就好,如果目的是深拷貝,用 structuredClone(data) 而不是 stringify / parse 來回。

Unexpected end of JSON input

JSON 還沒完結字串就結束了。常見情況兩種:空字串(JSON.parse(""))與被截斷的回應(中斷的網路請求或寫了一半的檔案)。先把原始字串的長度 log 出來。長度為 0 就代表問題在解析器之前,不在 JSON 語法本身。

Big5 編碼與 JSON 解析

台灣的舊系統、傳統業軟、以及不少政府與教育機構的資料交換格式,至今仍使用 Big5 編碼。Big5 是雙位元組編碼,與 UTF-8 的位元組結構完全不同。當一個以 Big5 儲存的 JSON 檔案被當作 UTF-8 來讀取時,會產生以下問題:

SyntaxError: Unexpected token ½½ in JSON at position 0

或更常見的情況——Big5 中的漢字位元組被 UTF-8 解碼器解讀為亂碼,導致解析器在第一個字符就失敗:

SyntaxError: Unexpected token ã½¥ in JSON at position 1

這裡 ã½¥ 其實是 Big5 位元組被誤讀為 UTF-8 後的結果。{ 在 Big5 中仍是 0x7B(與 UTF-8 相同),所以通常第一個字符能讀對,但緊接著的 Big5 雙位元組內容會被拆成多個無意義的 UTF-8 字符。

排查方法:

  1. file -bichardet 檢查檔案的實際編碼
  2. 如果是 Big5,先用 iconv 轉成 UTF-8 再解析:
iconv -f BIG5 -t UTF-8 config.json -o config-utf8.json
  1. 如果是 Big5 with BOM(某些 Windows 舊版編輯器會加上),先移除 BOM 再轉碼

Big5 與 UTF-8 的混用在以下場景特別容易發生:

  • 從舊版 ERP 系統或 Oracle 資料庫(AL32UTF8 以外的字元集)匯出的 JSON
  • 政府機關或學校的資料交換(許多仍使用 Big5 作為交換格式)
  • 用 Notepad++ 或 UltraEdit 以 Big5 編輯後,再用 VS Code(預設 UTF-8)開啟
  • 跨機器傳輸時,一台使用 Windows 繁體中文(系統預設 Big5),另一台使用 UTF-8

BOM 的問題在 Big5 環境中更複雜。Big5 with BOM 的標記是 0xFE 0xFF(兩個位元組),與 UTF-8 BOM(0xEF 0xBB 0xBF,三個位元組)完全不同。如果你的程式假設 BOM 固定是 UTF-8 的三位元組來移除,遇到 Big5 BOM 時會把前兩個正常位元組當作 BOM 刪掉,導致檔案內容損毀。

// 不安全:假設 BOM 一定是 UTF-8 的 3 位元組
const cleaned = text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text;

// 較安全:同時處理 UTF-8 BOM 與 Big5 BOM
function stripBom(buf) {
  if (buf.length >= 3 && buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) {
    return buf.slice(3); // UTF-8 BOM
  }
  if (buf.length >= 2 && buf[0] === 0xFE && buf[1] === 0xFF) {
    return buf.slice(2); // UTF-16 BE BOM / Big5 with BOM
  }
  return buf;
}

如果你的 JSON 來源你控制不了(例如第三方 API、政府開放資料、或舊版系統匯出),在程式端先做編碼偵測與轉換,再交給 JSON.parse,可以避開這一整類問題。

不同環境的錯誤訊息差異

同樣的語法錯誤,在不同運行環境下錯誤訊息不同。以下是主要環境的對照,方便你搜尋時找到對應描述:

環境 / 運行時典型錯誤訊息能判斷的事
Chrome / Node.js(V8)Unexpected token '<', "<!DOCTYPE html>" is not valid JSON開頭的 < 代表回傳的是 HTML 錯誤頁面而非 JSON。到網路標籤檢查實際回應
Firefox(SpiderMonkey)SyntaxError: JSON.parse: unexpected characterMDN 將此格式記錄為 JSON.parse: <原因>JSON_bad_parse在新分頁中開啟)。各引擎對應哪種文案 MDN 未明確標註
Pythonjson.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5同時顯示行號與欄位號
Java(Jackson)JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name同時指出預期的字符與實際字符

如果在瀏覽器看到 Unexpected token '<', "<!DOCTYPE html>" is not valid JSON(或舊版的 Unexpected token < in JSON at position 0),幾乎可以確定 API 回傳的是 HTML 錯誤頁面(404 或 500 頁面),不是 JSON。應該懷疑請求位址或狀態碼,而不是 JSON 語法。

Python 中常見的 Expecting value: line 1 column 1 (char 0) 也是同一類型。回應正文為空或傳了非 JSON 內容給 json.loads() 時會出現,先輸出回應內容本身是最快的排查方式。

排錯步驟

如果錯誤只有幾行,肉眼就能修。但 API 回應或大型檔案中,固定的排查順序能加快解決速度:

  1. 貼上 JSON 格式化器 查看錯誤行號與內容
  2. 看回報 position 稍微往前的位置。實際的錯誤常常在回報位置的前一行(例如第 10 行漏了逗號,到第 11 行才報錯)
  3. 檢查是否屬於上述 5 個原因(行尾多逗號、單引號、鍵沒加引號、註解、BOM)
  4. 如果是程式產生的 JSON,檢查序列化步驟。用字串拼接組 JSON 是最常見的 bug 溫床
  5. 如果是從 API 接收的 JSON,解析前先確認原始回應與編碼

用 fetch 接收回應時,先拿原始文字與狀態碼再解析,就能在一個地方區分 HTML 錯誤頁面、空回應、與截斷回應:

const response = await fetch("/api/data");
const raw = await response.text(); // 不用 response.json(),先拿原始文字

if (!response.ok) {
  console.error(`HTTP ${response.status}:`, raw.slice(0, 200));
} else if (!raw) {
  console.error("回應正文為空"); // Unexpected end of JSON input 的典型原因
} else {
  try {
    const data = JSON.parse(raw);
  } catch (e) {
    console.error("JSON 解析失敗:", e.message, raw.slice(0, 200));
  }
}

用 FormatArc 確認錯誤位置

知道 5 個原因後,從幾百行的 JSON 中用肉眼找問題位置仍然很吃力。使用 JSON 格式化器 貼上就能看到錯誤行號與內容。

操作只有三步:

  1. 開啟 JSON 格式化器
  2. 把 JSON 貼在左側編輯器
  3. 點執行按鈕

如果是合法 JSON,右側顯示格式化後的結果。如果有語法錯誤,錯誤訊息會包含行號,修正該行後重新執行即可。

所有處理都在瀏覽器內完成,包含 API 金鑰或個人資訊的 JSON 也可以安心使用。資料不會傳送到伺服器。

預防解析錯誤

比起出錯後再修,幾個習慣可以擋掉大部分的解析錯誤:

  • 產生 JSON 時用 JSON.stringify()(各語言的標準序列化器),不要用字串拼接
  • 編輯器開啟儲存時 JSON 驗證(VS Code、IntelliJ、Sublime Text 都支援)
  • CI 中加入驗證步驟。python -m json.tool < config.json 這樣的簡單檢查就能在部署前抓到壞檔案
  • 手動編輯時用能即時驗證的工具

另外,不要把業務資料貼到不確定的線上工具。FormatArc 所有處理在瀏覽器內完成,不向外傳輸資料。

什麼時候 JSON 不是對的格式

如果經常需要寫註解、處理多行字串、管理複雜嵌套設定,JSON 的規格限制會讓你很痛苦。YAML 支援註解、自然處理多行文字、設定檔案可讀性通常更好。

但 YAML 對縮排敏感,也有隱式型別轉換等獨特的坑。兩種格式需要時可以互轉,視場合分開使用就夠了。兩者的完整差異詳見 YAML 與 JSON 的差異

總結

JSON 解析錯誤的大多數案例都落在上述 5 個模式中。用錯誤訊息的 Unexpected tokenposition 作為線索可以縮小範圍,但檔案大時交給工具比較有效率。

遇到 Big5 編碼的 JSON 時,先確認編碼再解析,可以避開「亂碼」型解析錯誤。如果你的工作經常接觸舊系統的 Big5 資料,在程式端加上編碼偵測與轉換的步驟,會比事後修錯誤省事很多。