Javascript is required
错误排查发布于 2026-07-28审校于 2026-08-0810 分钟阅读

JSON Unexpected token:8 类原因与定位

JSON.parse 抛出 Unexpected token、unexpected character 或 bad parsing 时,真正有用的信息不是固定报错文案,而是“输入究竟是什么、解析器在哪个位置停止、该位置本应出现什么”。不同浏览器和 Node.js 版本的错误文字可能不同,因此本文以可复现的输入类型和定位流程为主,不提供可能破坏数据的通用自动修复正则。

JSONJSON.parseSyntaxErrorUnexpected token调试

一、先确认传给 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 语法层。

来源与延伸阅读

技术审校所依据的规范与权威参考资料。

相关文章

继续阅读

可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。

打开关联工具