← 全部文章

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 header。解析前先把原始响应的长度打到日志里。如果用 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 上解析、校验,并自动修复常见结构错误 —— 包括缺失的括号 —— 全部在浏览器里完成,没有数据被发送到任何服务器。