从 Unexpected token < 到 Extra data:一次讲清 JSON 解析错误的排查思路

JSON 解析报错看起来经常只有一句话,比如 Unexpected token <Unexpected end of JSON inputExtra data。这些错误本身并不复杂,但在线上排查时很容易走偏:有人会先怀疑解析库,有人会反复改字段类型,还有人会直接把响应体复制出来找格式问题。

更稳的思路是先判断一件事:你拿到的输入,到底是不是你以为的那段 JSON。

这篇文章按实际排查顺序整理几类常见 JSON 问题,包括接口返回 HTML、空响应、格式不严格、多个 JSON 文档拼在一起、转义错误、结构不匹配、重复 key 和大整数精度问题。

先看输入,不要先看 parse

遇到 JSON 解析失败时,我一般不会第一时间盯着 JSON.parse 或后端反序列化异常,而是先打印这些信息:

js 复制代码
console.log('status:', response.status)
console.log('content-type:', response.headers.get('content-type'))

const text = await response.text()
console.log('body preview:', text.slice(0, 200))

原因很简单:很多"JSON 解析错误"并不是 JSON 写错了,而是客户端根本没有拿到 JSON。

比如:

  • 接口路径写错,返回了 HTML 404 页面
  • 网关拦截,返回了登录页
  • 服务端异常,返回了错误模板
  • 响应为空,但代码仍然执行了解析
  • 返回内容被截断,只拿到半段 JSON

先确认状态码、响应头和响应体前几百个字符,可以省掉很多无效排查。

Unexpected token <:你拿到的通常是 HTML

前端最常见的 JSON 报错之一是:

text 复制代码
Unexpected token < in JSON at position 0

这个错误的关键信息是 <,它通常不是 JSON 里的字符,而是 HTML 的开头。

例如接口真实返回的是:

html 复制代码
<!doctype html>
<html>
  <head><title>404</title></head>
  <body>Not Found</body>
</html>

但代码仍然按 JSON 去解析:

js 复制代码
const res = await fetch('/api/user/detail')
const data = await res.json()

这时 res.json() 读到第一个字符 <,自然会报错。

排查这种问题时,重点看三处:

js 复制代码
const res = await fetch('/api/user/detail')
const text = await res.text()

console.log(res.status)
console.log(res.headers.get('content-type'))
console.log(text.slice(0, 200))

如果 content-typetext/html,或者响应体里出现 <!doctype html><html><title>,就不要继续查 JSON 格式了,应该先查接口地址、代理配置、登录态、网关规则或服务端错误页。

Unexpected end:响应为空或 JSON 不完整

另一个高频错误是:

text 复制代码
Unexpected end of JSON input

它的意思是解析器读到结尾了,但 JSON 还没结束。

常见场景有两个。

第一种是空字符串:

js 复制代码
JSON.parse('')

第二种是 JSON 被截断:

js 复制代码
JSON.parse('{"name":"Tom","age":')

在线上接口里,空响应经常来自这些情况:

  • HTTP 204 本来就没有响应体
  • 删除、提交类接口只返回状态码
  • 服务端异常导致连接提前断开
  • 代理或网关超时,响应体没有完整返回

比较稳的处理方式是先拿文本,再按业务约定判断是否需要解析:

js 复制代码
const res = await fetch('/api/profile')
const text = await res.text()

if (!text.trim()) {
  return null
}

const data = JSON.parse(text)

如果接口约定一定返回 JSON,那空响应就不是前端兜底能解决的问题,应该让服务端统一返回结构,例如:

json 复制代码
{
  "code": 0,
  "data": null,
  "message": "ok"
}

Expecting property name:把 JS 对象写法当成 JSON

很多人手写 JSON 时,会不小心写成 JavaScript 对象字面量:

js 复制代码
{
  name: 'Tom',
  age: 18,
}

这在 JS 代码里可以成立,但不是合法 JSON。

严格 JSON 需要满足几个基本规则:

json 复制代码
{
  "name": "Tom",
  "age": 18
}

常见错误包括:

  • key 没有用双引号包住
  • 字符串用了单引号
  • 最后一项多了逗号
  • 写了注释
  • 使用了 undefined

下面这些都不是合法 JSON:

json 复制代码
{
  "name": "Tom",
}
json 复制代码
{
  "name": 'Tom'
}
json 复制代码
{
  // user name
  "name": "Tom"
}

JSON 的定位是数据交换格式,不是 JS 配置文件。如果你需要注释、尾逗号、变量或表达式,那应该考虑 JSON5、YAML,或者直接使用 JS/TS 配置文件,而不是把它们混进 JSON。

Extra data:一次 parse 只能处理一个 JSON 文档

有些后端日志或数据流里,会出现这种内容:

json 复制代码
{"id":1}
{"id":2}

它看起来像 JSON,但整体并不是一个合法 JSON 文档。因为一次 JSON.parse 只能解析一个完整值,解析完第一个对象后,后面还有额外内容,于是就会出现类似 Extra data 的错误。

正确写法要么是数组:

json 复制代码
[
  {"id": 1},
  {"id": 2}
]

要么明确使用 NDJSON,也就是一行一个 JSON:

json 复制代码
{"id":1}
{"id":2}

如果是 NDJSON,就不要直接用 JSON.parse 解析整个文本,而应该逐行处理:

js 复制代码
const rows = text
  .split('\n')
  .filter(Boolean)
  .map(line => JSON.parse(line))

这里要注意,普通 JSON 和 NDJSON 是两种不同的数据格式。前后端接口、日志采集、消息队列消费时,要提前约定清楚。

Invalid escape:反斜杠和控制字符问题

JSON 字符串里的反斜杠需要正确转义。

比如 Windows 路径如果这样写:

json 复制代码
{
  "path": "C:\new\test"
}

这里的 \n 会被当成换行,\t 会被当成 tab,结果可能不是你想要的路径。

正确写法是:

json 复制代码
{
  "path": "C:\\new\\test"
}

如果字符串里有换行,也不能直接把真实换行塞进 JSON 字符串:

json 复制代码
{
  "message": "hello
world"
}

应该写成:

json 复制代码
{
  "message": "hello\nworld"
}

这类问题经常出现在日志内容、正则表达式、文件路径、用户复制粘贴的文本里。排查时不要只看格式化后的展示,最好看原始字符串。

JSON 合法,不代表业务结构正确

有时候 JSON 本身是合法的,但业务解析仍然失败。

比如后端期望 age 是数字:

json 复制代码
{
  "name": "Tom",
  "age": 18
}

实际传过来的是字符串:

json 复制代码
{
  "name": "Tom",
  "age": "18"
}

对 JavaScript 来说,这段 JSON 可以正常解析;但对 Java、Go、Rust 这类强类型服务来说,反序列化到 DTO 或 struct 时就可能失败。

这时问题不在 JSON 语法,而在 schema 不匹配。

排查方向应该变成:

  • 字段名是否一致
  • 字段类型是否一致
  • 必填字段是否缺失
  • 数组和对象是否写反
  • 空值是否允许
  • 枚举值是否在合法范围内

例如接口约定是:

json 复制代码
{
  "tags": ["frontend", "json"]
}

但实际传成:

json 复制代码
{
  "tags": "frontend,json"
}

这不是语法错误,但依然会导致业务解析失败。

不报错但更危险:重复 key

重复 key 通常不会让 JSON 解析器报错,但会带来隐蔽问题。

例如:

json 复制代码
{
  "role": "user",
  "role": "admin"
}

很多解析器会保留最后一个值,结果变成:

json 复制代码
{
  "role": "admin"
}

不同语言、不同库对重复 key 的处理不一定完全一致。对配置、权限、签名、风控字段来说,这类问题尤其危险。

如果 JSON 来自外部输入,建议在 schema 校验或解析层增加重复 key 检测,而不是只依赖默认解析结果。

大整数精度:解析成功也可能已经错了

JavaScript 里的 number 使用双精度浮点数,安全整数范围有限。

比如:

js 复制代码
JSON.parse('{"id": 9007199254740993}')

解析后可能已经丢失精度。

对于订单号、雪花 ID、数据库主键这类大整数,不建议作为数字传给前端,最好改成字符串:

json 复制代码
{
  "id": "9007199254740993"
}

这类问题不会表现为 JSON 解析失败,但会在后续查询、比对、跳转详情页时出错。看到长 ID 时,要特别注意它是不是应该用字符串承载。

我在线上会怎么排查

实际排查时,可以按这个顺序走:

第一步,看 HTTP 状态码。

如果是 401、403、404、500,优先查鉴权、路由、网关、服务端异常,不要先查 JSON 格式。

第二步,看 Content-Type

如果不是 application/json 或约定的 JSON 类型,要确认是否拿到了 HTML、纯文本、下载文件或错误页。

第三步,看响应体前 200 个字符。

开头是 <,多半是 HTML;开头为空,先查空响应;开头是 {[,再继续看 JSON 结构。

第四步,把原始文本单独校验。

如果只是想快速确认一段文本是否符合严格 JSON 语法,可以用在线校验器辅助检查,例如 JSON Toolbox:jsontoolbox.cn 。但线上问题最终还是要以接口响应、日志和服务端返回为准,在线工具只适合做临时验证。

第五步,对照接口 schema。

JSON 语法合法之后,再看字段类型、必填字段、数组对象结构、枚举值和空值约定。

常见错误速查表

报错或现象 常见原因 排查重点
Unexpected token < 返回了 HTML 看响应体开头、状态码、接口路径、登录态
Unexpected end of JSON input 空响应或内容截断 看响应体长度、204、网关超时、服务端异常
Expecting property name key 没加双引号或用了 JS 对象写法 检查双引号、尾逗号、注释、单引号
Extra data 多个 JSON 文档拼在一起 区分 JSON 数组和 NDJSON
Invalid escape 字符串转义错误 检查反斜杠、换行、路径、正则
解析成功但业务失败 schema 不匹配 检查字段名、类型、必填、数组对象结构
解析成功但值不对 大整数精度丢失 长 ID 改成字符串
权限字段异常 重复 key 被覆盖 增加重复 key 检测

总结

JSON 解析失败时,最重要的不是记住每一种报错文案,而是先确认输入。

一个比较可靠的判断顺序是:

text 复制代码
状态码 -> Content-Type -> 原始响应体 -> JSON 语法 -> 业务 schema -> 精度和重复 key

很多问题走到第三步就能定位:拿到的不是 JSON、响应为空、响应被截断、或者后端返回了 HTML 错误页。

只有确认输入确实是 JSON 之后,才需要继续检查双引号、尾逗号、转义、数组对象结构和字段类型。这样排查会比直接盯着解析异常更快,也更容易定位到真正的责任边界。

相关推荐
疯狂SQL6 天前
手写高性能在线 JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
typescript·json·next.js·web worker·前端性能优化·esbuild·源码实战
terry60011 天前
5G视频短信服务商选型全攻略:通道资源、架构能力与成本评估2026最新标准
大数据·人工智能·5g·json·asp.net·信息与通信·数据库架构
前网易架构师-高司机11 天前
带标注的辣椒病叶数据集,识别率95.9%,可识别三种病害和健康叶子,9916张图,支持yolo,coco json,voc xml,文末有模型训练代码
yolo·json·数据集·病害·叶病·病叶·辣椒
PixelBai11 天前
JSON扁平化使用教程:从入门到精通
json
渔舟唱晚,雁阵惊寒12 天前
CSDN博客内容丢失如何恢复?
json
衣乌安、12 天前
JSON-RPC协议
网络协议·rpc·json
PixelBai12 天前
JSON过滤使用教程:从入门到精通
javascript·chrome·json
PixelBai12 天前
JSON过滤实际应用场景案例
json