JSON Unexpected token:8 类原因与定位
JSON.parse 抛出 Unexpected token、unexpected character 或 bad parsing 时,真正有用的信息不是固定报错文案,而是“输入究竟是什么、解析器在哪个位置停止、该位置本应出现什么”。不同浏览器和 Node.js 版本的错误文字可能不同,因此本文以可复现的输入类型和定位流程为主,不提供可能破坏数据的通用自动修复正则。
一、先确认传给 JSON.parse 的确实是字符串
JSON.parse 会先把非字符串参数转换为字符串。把已经解析好的对象再次传入时,对象通常会变成 [object Object],随后在开头附近报错。这个问题不是 JSON 内容损坏,而是调用层级重复。
入口处先检查 typeof。若数据来自 fetch,response.json() 已经完成解析;不要再对返回对象调用 JSON.parse。
const objectValue = { id: 1 };
try {
JSON.parse(objectValue as unknown as string);
} catch (error) {
console.error(error); // 各运行时的具体文案可能不同
}
function parseJsonText(input: unknown): unknown {
if (typeof input !== 'string') {
throw new TypeError(`Expected JSON text, received ${typeof input}`);
}
return JSON.parse(input);
}二、8 类高频原因应按输入来源分类
语法错误只是其中一类。接口调试时,应先区分“拿到的根本不是 JSON”和“拿到了 JSON 形状但语法非法”,否则容易在错误层面反复修改。
- 返回 HTML:错误页、登录页或反向代理页面通常以 < 开头。
- 重复解析对象:response.json() 的结果或框架已反序列化的数据再次进入 JSON.parse。
- 单引号或未加引号的键:JavaScript 对象字面量写法不等于 JSON。
- 尾随逗号或注释:JSON 不接受对象/数组末尾逗号,也不支持 // 与 /* */ 注释。
- 字符串中出现未转义的换行、Tab、双引号或反斜杠。
- 非法数字:NaN、Infinity、01、1.、十六进制字面量都不是标准 JSON 数字。
- 空响应或传输被截断:常见表现为 unexpected end of JSON input。
- BOM、多个根值或根值后还有额外字符,例如 {}{} 或 {}debug。
三、接口请求先检查状态码、Content-Type 和原始正文
直接调用 response.json() 会把网络层和解析层错误叠在一起。排查阶段可以先读取 text,记录状态码与 Content-Type,再决定是否解析。注意 Response 的 body 只能消费一次;下面的函数已经统一在 text 上处理。
不能只相信 Content-Type,因为配置错误的服务也可能把 HTML 标成 application/json;最终仍要检查正文。
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url, {
headers: { Accept: 'application/json' },
});
const contentType = response.headers.get('content-type') ?? '';
const text = await response.text();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);
}
if (!contentType.includes('application/json')) {
throw new Error(`Expected JSON, got ${contentType || 'unknown type'}`);
}
if (text.trim() === '') {
throw new Error('Expected JSON, got an empty response body');
}
try {
return JSON.parse(text) as T;
} catch (error) {
throw new Error(`Invalid JSON: ${(error as Error).message}`);
}
}四、最常见的语法差异:JSON 不是 JavaScript 对象字面量
下面几种文本在 JavaScript 源码里可能看起来熟悉,但都不能直接作为标准 JSON 解析。修复时应修改数据生产端,而不是在客户端用大范围替换猜测作者意图。
const invalidSamples = [
"{'name':'Tiny'}", // 单引号
'{name:"Tiny"}', // 键名未使用双引号
'{"name":"Tiny",}', // 尾随逗号
'{"enabled":true // ok\n}', // 注释
'{"value":NaN}', // 非法数字
'{"value":01}', // 前导零
];
for (const sample of invalidSamples) {
try {
JSON.parse(sample);
} catch (error) {
console.log(sample, (error as Error).message);
}
}五、用错误位置截取上下文,而不是打印整份文件
V8 常在错误消息中提供 position,Firefox 可能提供 line 和 column,其他运行时的格式也可能不同。诊断函数应把位置解析视为“可选增强”,不能假设所有错误都包含同一种字段。
截取前后字符并显示 JSON.stringify 后的片段,可以让换行、Tab 和不可见字符显形。
function diagnoseJson(text: string): {
ok: true;
value: unknown;
} | {
ok: false;
message: string;
position?: number;
context?: string;
} {
try {
return { ok: true, value: JSON.parse(text) };
} catch (error) {
const message = (error as Error).message;
const match = message.match(/position\s+(\d+)/i);
const position = match ? Number(match[1]) : undefined;
if (position === undefined) {
return { ok: false, message };
}
const start = Math.max(0, position - 30);
const end = Math.min(text.length, position + 31);
return {
ok: false,
message,
position,
context: JSON.stringify(text.slice(start, end)),
};
}
}六、字符串转义错误必须在生成阶段修复
用户输入、Windows 路径和多行文本最容易在手工拼接 JSON 时破坏语法。正确做法是先构造 JavaScript 对象,再交给 JSON.stringify;不要自己拼双引号、反斜杠和换行。
如果字段本身保存另一段 JSON 字符串,需要按层 JSON.stringify 和 JSON.parse,具体方法见 /articles/nested-json-escaping。
const userInput = '第一行\n第二行 "quoted" C:\\temp';
// 错误:手工拼接很容易漏掉转义
// const text = '{"message":"' + userInput + '"}';
const text = JSON.stringify({ message: userInput });
const parsed = JSON.parse(text) as { message: string };
console.assert(parsed.message === userInput);七、不要把“自动修复”当作默认策略
删除所有反斜杠、把单引号全换成双引号、用正则移除注释,都可能改变字符串字段中的真实数据。只有在输入格式被明确规定为 JSON5、JSONC 等非标准格式时,才应使用对应解析器,并在边界处转换成标准 JSON。
对于第三方数据,优先拒绝并返回清晰错误;对于自己控制的数据,修复序列化端并增加覆盖具体坏样本的测试。
- 204 No Content 不应强制解析 JSON。
- 一个响应只能有一个 JSON 根值,多个对象不能直接连续拼接。
- 空字符串不是合法 JSON;表示空值应使用 null。
- BOM 或前导不可见字符应先确认来源,再在文件读取边界做定向处理。
八、验证流程、FAQ 与结论
验证顺序:保存原始文本;确认类型和来源;检查 HTTP 状态与 Content-Type;用最小样本复现;根据错误位置查看上下文;修复生产端;最后用原坏样本回归。可使用首页 JSON Formatter(/)快速验证文本,但不要把敏感数据复制到不受信任的第三方页面。
问:为什么 Chrome 与 Firefox 的报错文字不同?答:ECMAScript 规定抛出 SyntaxError,但不要求各引擎使用完全相同的消息。
问:Unexpected token < 是否一定是 404?答:不一定,只能说明开头很可能是 HTML;还可能是登录页、WAF 页面或服务端错误模板。
问:try...catch 能修复 JSON 吗?答:不能,它只能阻止异常中断流程,并提供降级或错误提示。
结论:定位 Unexpected token 的核心不是背错误文案,而是保留原始输入并判断错误发生在网络层、调用层还是 JSON 语法层。
来源与延伸阅读
技术审校所依据的规范与权威参考资料。
相关文章
JSON 大整数精度丢失:Number 安全范围与解决方案
说明 JSON 大整数进入 JavaScript 后为什么会被舍入,演示 Number.MAX_SAFE_INTEGER 边界,并给出字符串契约、BigInt 转换和序列化的可验证方案。
错误排查JSON 对象键顺序不同为何 Diff:结构化比较方法
解释 JSON 文本比较与结构化比较的区别,演示如何递归规范化对象键且保留数组顺序,并列出数字、重复键和数组语义等边界条件。
错误排查JSON 转 SQL 类型推断:MySQL 与 PostgreSQL 的边界
分开说明 MySQL 与 PostgreSQL 对 JSON、数字、字符串、数组、对象和 NULL 的类型建议,解释为什么样本推断不能替代明确的数据库 schema。
继续阅读
可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。
打开关联工具