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,或者用二分法截取前半段单独解析,第一段解析失败的边界就是真实出错点。