⭐ 推荐:社区规则条款 V1.0

js JSON.parse 报错怎么排查

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-20 14:04 ·4 浏览 ·0 回复
内容摘要

排查 JSON.parse 报错时,应先确认传入字符串是否符合 JSON 规范;核心步骤是打印原始字符串,根据报错中的 position 截取上下文来定位字符,并对照六类高频原因检查:单引号、尾逗号、误传对象或 undefined、未转义换

JSON.parse 报错的根本原因几乎从来不是解析器有问题,而是你传给它的字符串不符合 JSON 规范(RFC 8259)。排查只需要三步:先把原始字符串原样打印出来,再从报错信息里取出 position 定位到具体字符,最后对照几个高频错误特征定位。整个过程通常 5 分钟内就能完成。

JSON.parse 最常见的 6 种报错原因是什么

结论:90% 的报错来自下面 6 种情况,其中单引号和「把对象当字符串传进去」占了大多数。

  1. 单引号:JSON.parse("{'a':1}") 直接报错,JSON 标准只认双引号,同理中文全角引号 “” 也不行。
  2. 尾逗号:{"a":1,}、[1,2,] 都是非法 JSON,JS 对象字面量允许但 JSON 不允许。
  3. 传了对象或 undefined:JSON.parse({}) 会把参数隐式转成字符串 "[object Object]" 再解析,报 Unexpected token o;JSON.parse(undefined) 解析的是字符串 "undefined"。
  4. 未转义的换行和制表符:字符串值里直接出现 \n(真实换行,不是 \\n)会报 Unexpected token,必须写成转义序列。
  5. BOM 头:后端返回的 UTF-8 文件带 \uFEFF,开头就解析失败,用 str.replace(/^\uFEFF/, '') 或 str.trim() 去掉(trim 会移除 \uFEFF)。
  6. 非法字面量:NaN、Infinity、undefined、01、+1、.5、注释 // 在 JSON 里全部不合法。

报错里的 position 怎么用来定位问题

结论:position 是从 0 开始的字符下标,用 str.slice(position - 30, position + 30) 打印上下文,出错字符一眼就能看到。

旧版 V8(Chrome 114 之前、Node 20 之前)的报错形如 Unexpected token } in JSON at position 15,直接正则取数字即可。较新的 V8 会把原文片段一起打出来,比如 Unexpected token '}', ..."a":1,} is not valid JSON,此时直接在 message 里看出错字符。

一段可以直接复用的排查函数:

function debugParse(str) {
  try {
    return JSON.parse(str);
  } catch (e) {
    const pos = Number((e.message.match(/position (\d+)/) || [])[1] ?? 0);
    console.error('总长度:', str.length, '出错位置:', pos);
    console.error('上下文:', JSON.stringify(str.slice(Math.max(0, pos - 30), pos + 30)));
    console.error('该字符:', JSON.stringify(str[pos]), 'charCode:', str.charCodeAt(pos));
    throw e;
  }
}

如果字符串很长(比如几十 KB 的接口返回),position 加上下文窗口就够了,不需要肉眼扫全文;实在定位不到就用二分法:把字符串从中间切一半分别 parse,哪一半报错就继续切。

为什么 JSON.parse 会报 Unexpected token o / u / <

结论:这三个字符分别对应三种典型输入——o 是对象被转成了 "[object Object]",u 是字符串 "undefined",< 是拿到了 HTML 页面。

  • Unexpected token o ... position 1:你传的已经是对象了,JSON.parse 把它 ToString 成 "[object Object]",第 0 位 [ 合法,第 1 位 o 非法。修复方式是不用 parse,直接用对象;如果数据来源不确定,先 typeof data === 'string' 判断。
  • Unexpected token u ... position 0:典型场景是 const data = JSON.parse(localStorage.getItem('key')),而 key 不存在时 getItem 返回 null……null 会被转成 "null" 从而解析成功返回 null,所以 u 更多来自 JSON.parse(undefined) 或后端字段本身就是字符串 "undefined"。
  • Unexpected token < ... position 0:几乎 100% 是接口返回了 HTML(404 页、登录跳转页、网关错误页)。排查方法是先 await res.text() 打印前 200 个字符,再决定 parse 不 parse。
  • Unexpected end of JSON input:输入是空字符串 "",或者返回被截断了(大响应体读取不完整)。

接口返回的 JSON 前端解析失败怎么排查

结论:先看响应状态码和 Content-Type,再看原始文本,最后才 parse——不要直接调 res.json()。

res.json() 内部就是 JSON.parse(await res.text()),报错信息会丢掉原文。可靠写法是:

const text = await res.text();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
if (!/application\/json/.test(res.headers.get('content-type') || '')) {
  throw new Error('非 JSON 响应: ' + text.slice(0, 200));
}
const data = JSON.parse(text);

另外两个隐蔽坑:一是双重编码,后端把 JSON 序列化了两次,前端拿到的是 "{\"a\":1}" 这种带转义的字符串,需要 JSON.parse(JSON.parse(s));二是大整数精度丢失,JSON.parse('{"id":1234567890123456789}') 得到的是 1234567890123456800,超过 Number.MAX_SAFE_INTEGER(9007199254740991)的 ID 必须让后端返回字符串,或者用 json-bigint 这类库解析。

怎么一次性避免 JSON.parse 报错

结论:在数据入口做一次类型和格式校验,比在每个调用点 try/catch 更省事。

具体做法:所有网络响应统一走一个 safeParse 包装函数,内部 try/catch 并打印上下文;对确实不严格的数据源(配置文件、手写 mock)改用 JSON5 或 relaxed-json 解析,它们支持单引号、尾逗号和注释;写入端统一用 JSON.stringify 生成,禁止手拼字符串。还有一个常见误区:不要用 str.replace(/'/g, '"') 修单引号,它会破坏字符串值里本来就有的撇号,比如 {"name":"O'Brien"} 会被改坏。

总结:JSON.parse 报错就是「输入的字符串不是 JSON」,先用 position 定位到具体字符,再对照单引号、尾逗号、BOM、HTML 响应、undefined 这几个高频原因,基本一次就能修好;长期方案是在数据入口统一包装解析函数,并把大整数和双重编码这两类隐蔽问题在接口层就处理掉。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2318.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~