js JSON.parse 报错怎么排查
排查 JSON.parse 报错时,应先确认传入字符串是否符合 JSON 规范;核心步骤是打印原始字符串,根据报错中的 position 截取上下文来定位字符,并对照六类高频原因检查:单引号、尾逗号、误传对象或 undefined、未转义换
JSON.parse 报错的根本原因几乎从来不是解析器有问题,而是你传给它的字符串不符合 JSON 规范(RFC 8259)。排查只需要三步:先把原始字符串原样打印出来,再从报错信息里取出 position 定位到具体字符,最后对照几个高频错误特征定位。整个过程通常 5 分钟内就能完成。
JSON.parse 最常见的 6 种报错原因是什么
结论:90% 的报错来自下面 6 种情况,其中单引号和「把对象当字符串传进去」占了大多数。
- 单引号:
JSON.parse("{'a':1}")直接报错,JSON 标准只认双引号,同理中文全角引号“”也不行。 - 尾逗号:
{"a":1,}、[1,2,]都是非法 JSON,JS 对象字面量允许但 JSON 不允许。 - 传了对象或 undefined:
JSON.parse({})会把参数隐式转成字符串"[object Object]"再解析,报Unexpected token o;JSON.parse(undefined)解析的是字符串"undefined"。 - 未转义的换行和制表符:字符串值里直接出现
\n(真实换行,不是\\n)会报Unexpected token,必须写成转义序列。 - BOM 头:后端返回的 UTF-8 文件带
\uFEFF,开头就解析失败,用str.replace(/^\uFEFF/, '')或str.trim()去掉(trim会移除\uFEFF)。 - 非法字面量:
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 这几个高频原因,基本一次就能修好;长期方案是在数据入口统一包装解析函数,并把大整数和双重编码这两类隐蔽问题在接口层就处理掉。
转载请注明出处,版权归原作者所有。