← 全部文章

Unexpected End of JSON Input:為什麼會出現以及如何修復

解析器在結構完成前耗盡了輸入。原因從被截斷的 API 回應到未關閉括號,再到空字串都有。五種模式,五種修復。

SyntaxError: Unexpected end of JSON input 表示解析器在 JSON 結構還沒完成前就讀到了字串末尾。資料被截斷。本文展示這種情況發生的所有常見方式,以及每一種該怎麼修。

這個錯誤是什麼意思

JSON.parse() 是一個狀態機。它一次一個字元地讀,追蹤打開的方括號、花括號和字串分隔符。當它走到輸入末尾,但仍「在某個東西裡面」 —— 物件、陣列或字串 —— 它就會拋:

SyntaxError: Unexpected end of JSON input

這個錯誤沒有行號、沒有位置,因為問題本身就是內容缺失 —— 沒有東西可以指。

原因 1 —— API 回應被截斷

這是真實世界中最常見的原因。伺服器發了一個大型 JSON body,要麼網路提早斷線、要麼回應還沒讀完就被讀了、要麼代理或 CDN 的緩衝限制被觸發。

// 實際收到的(回應到一半連線斷了):
'{"users":[{"id":1,"name":"Ada"},{"id":2,"na'

JSON.parse('{"users":[{"id":1,...') // SyntaxError: Unexpected end of JSON input

修復: 檢查 HTTP 狀態碼和 Content-Length 標頭。解析前先把原始回應的長度寫入日誌。如果用 fetch,總是 await response.json(),而不是先讀 response.text() 再自己解析 —— 瀏覽器內建的 JSON 解析器對截斷會給出更清晰的錯誤。

// 正確
const data = await response.json();

// 脆弱 —— 把截斷錯誤藏起來
const text = await response.text();
const data = JSON.parse(text);

原因 2 —— 物件或陣列沒閉合

JSON 是手寫的,或由程式碼生成時少寫了一個收尾的方括號或花括號。

JSON.parse('{"name":"Ada","plan":"pro"')
// 少了收尾的 }
// SyntaxError: Unexpected end of JSON input

JSON.parse('[1, 2, 3')
// 少了收尾的 ]
// SyntaxError: Unexpected end of JSON input

修復: 用 JSON linter,或把字串貼到 JSON Fix —— 它會找出沒閉合的那個分隔符,並且在寬鬆模式下能自動補上缺的括號。

原因 3 —— 字串沒閉合

一個字串值沒用收尾的雙引號結束,常見是因為字串內部帶了一個未跳脫的雙引號:

JSON.parse('{"title":"He said "hello" to her"}')
//                              ^ 未跳脫的引號提前結束了字串
// SyntaxError: Unexpected end of JSON input

// 正確 —— 內部引號用反斜線跳脫:
JSON.parse('{"title":"He said \"hello\" to her"}')

修復: 把內部雙引號跳脫成 \",或一開始就用 JSON.stringify() 來建立 JSON,而非手工拼字串。

原因 4 —— 空字串

把空字串或只有空白的字串傳給 JSON.parse() 也會拋同樣的錯 —— 根本沒有輸入可解析。

JSON.parse('')   // SyntaxError: Unexpected end of JSON input
JSON.parse('  ') // SyntaxError: Unexpected end of JSON input
JSON.parse()     // SyntaxError: Unexpected token u…(undefined 被轉成字串)

這經常發生在表單欄位、localStorage 的 key、或環境變數為空的時候。

// 解析前先 guard
const raw = localStorage.getItem('session');
if (!raw) return null;
return JSON.parse(raw);

原因 5 —— 串流回應讀得太早

在消費串流 API 或 WebSocket 時,很容易在整條訊息還沒到齊之前就先去解析一個 chunk。

// ❌ 試圖解析不完整的 chunk
ws.onmessage = (event) => {
  const data = JSON.parse(event.data); // 可能不完整
};

// ✓ 緩衝到訊息邊界(應用層分幀)
let buffer = '';
ws.onmessage = (event) => {
  buffer += event.data;
  if (buffer.endsWith('\n')) {        // 或檢查已知的分隔符
    const data = JSON.parse(buffer.trim());
    buffer = '';
  }
};

診斷截斷:Content-Length 與串流讀取器

兩個具體技巧,用來確認回應是否真的被截短:

  • 對比 Content-Length 如果伺服器有設,可以直接偵測到短讀:
    const res = await fetch(url);
    const expected = Number(res.headers.get('Content-Length') ?? NaN);
    const text = await res.text();
    if (Number.isFinite(expected) && text.length !== expected) {
      throw new Error(`truncated: got ${text.length} of ${expected} bytes`);
    }

    注意:Content-Length 在分塊或壓縮回應中可能不存在。

  • 用串流 body 讀取器讀。res.body.getReader() 可以讓你逐塊觀察資料到達,並察覺串流在 payload 中間關閉 —— 這就是連線中斷或代理逾時的徵兆。如果串流被中止,瀏覽器會以 fetch rejection 形式暴露底層網路失敗;配合解析器錯誤一起看會更準確。

快速診斷清單

  • 在呼叫 parse 之前,記錄 typeof inputinput.length
  • 如果長度看起來太短,回應被截斷了 —— 看網路日誌。
  • 如果長度是 0,來源(API、儲存、環境變數)什麼都沒回 —— 加 guard。
  • 如果長度看起來對,把字串貼到 JSON Fix 找結構問題。

在正式環境優雅地兜底

function safeParse(text, fallback = null) {
  if (!text || !text.trim()) return fallback;
  try {
    return JSON.parse(text);
  } catch {
    console.error('JSON parse failed, input length:', text.length);
    return fallback;
  }
}

常見問題

「Unexpected end of JSON input」由什麼引起?

解析器在仍處於物件、陣列或字串內部時走到字串末尾 —— 資料不完整。最常見的真實原因是 API 回應被截斷;其他情況包含未閉合的括號、未結束的字串、空字串。

為什麼沒有行號或位置?

因為問題是內容缺失,而不是某個字元錯了。解析器沒有東西可指 —— 它只是在結構閉合之前把輸入用完了。

空字串的情況怎麼修?

解析前加 guard:if (!raw || !raw.trim()) return fallback;。空表單欄位、localStorage 中缺失的 key、未設定的環境變數是最常見來源。

在正式環境怎麼優雅處理?

用一個 safeParse 輔助函式把 JSON.parse() 包起來,失敗時回傳一個 fallback(見上方片段),並把輸入長度寫入日誌,便於區分截斷與結構問題。完整的模式集請見 在 JavaScript 中處理壞掉的 JSON

在瀏覽器中修復被截斷的 JSON

有一段壞掉的 JSON 字串需要檢查或修復?JSON Fix 在 fixjson.org 上解析、驗證並自動修復常見結構錯誤 —— 包括缺失的括號 —— 全部在瀏覽器中完成,沒有資料被送往任何伺服器。