处理 JSON 的时候,几乎都会遇到 SyntaxError: Unexpected token 这个报错。正在调 API 响应、读取配置文件、处理导出的文件,突然一行报错蹦出来,工作就卡住了。
报错位置不好找的话,直接把内容粘贴到 JSON 格式化工具,它会告诉你具体哪一行、哪个字符出了问题。
知道了原因,修复本身不难。但光看报错信息,很多时候判断不了到底是什么问题。这篇文章讲怎么读 JSON parse 报错,以及最常见的 5 类原因,每个都配了具体示例。
怎么读 SyntaxError: Unexpected token
在浏览器或 Node.js 里解析 JSON,遇到语法问题会返回这样的错误:
SyntaxError: Unexpected token ' in JSON at position 42
这条信息包含三个部分:
Unexpected token '— 解析器遇到了一个它不认识的字符,这里是'(单引号)at position 42— 错误出现在 JSON 字符串从第 1 个字符起算的第 42 个字符处SyntaxError— 语法本身有问题(不是数据内容的问题,是写法的问题)
position 指的是字符位置,不是字节位置。换行符和空格也算在里面,所以在大文件里手动数位置很不现实。用 JSON 格式化工具 一类的工具可以立刻定位到出错的位置。
常见 5 大原因
1. 尾逗号(trailing comma)
JavaScript 里数组和对象末尾可以加逗号,但 JSON 规范不允许尾逗号。
{
"name": "Alice",
"age": 30,
}
最后一个 30, 的逗号要去掉:
{
"name": "Alice",
"age": 30
}
如果你的编辑器格式化工具会自动在末尾加逗号,建议在 JSON 文件上关掉这个选项。
2. 单引号
Python 字典和 JavaScript 对象里可以用 ',但 JSON 只认双引号 "。
{'name': 'Alice'}
这不是合法的 JSON,需要全部换成双引号:
{"name": "Alice"}
在 Python 里把字典变成 JSON 字符串时,用 json.dumps() 而不是 str()。str() 会输出单引号。
3. 键没加引号
JavaScript 对象里键可以不加引号,JSON 要求所有键必须用双引号包起来。
{name: "Alice"}
正确写法:
{"name": "Alice"}
4. 注释
配置文件里经常犯的错误。JSON 规范没有注释语法。
{
// 用户姓名
"name": "Alice"
}
// 和 /* */ 都要去掉。tsconfig.json 这类文件用的是 JSONC(JSON with Comments)扩展格式,允许注释,但标准 JSON 解析器不认。JSONC、JSON5、_comment 字段、移除脚本各自的取舍,见 JSON 可以注释吗?。
如果配置文件确实需要保留注释,可以考虑用 YAML。YAML 原生支持 # 注释,两个格式之间可以用 YAML JSON 转换工具 互相转。
5. BOM(字节顺序标记)
UTF-8 文件开头如果带有 BOM(\uFEFF),解析器会把它当作非法字符。
SyntaxError: Unexpected token in JSON at position 0
报错位置是 0、又看不到可疑字符的时候,大概率是 BOM。在编辑器里重新保存为无 BOM 的 UTF-8,或者在代码里去掉再解析:
const cleaned = text.replace(/^\uFEFF/, '');
const data = JSON.parse(cleaned);
其他原因
除了上面 5 个,下面这些也会导致解析报错。
括号不匹配
少了一个 } 或 ],通常会在文件末尾报错。嵌套层次多的 JSON,肉眼很难找到哪个括号没配对。用支持括号高亮的格式化工具能省很多时间。
字符串里的控制字符
字符串里混入了未转义的换行符或制表符会直接报错。换行需要写成 \n。
{"message": "Hello
World"}
正确写法:
{"message": "Hello\nWorld"}
前导零的数字
JSON 数字不允许前导零。007 是非法值,要改成 7。
undefined 和 NaN
JavaScript 的 undefined 和 NaN 不能当 JSON 值用。包含 undefined 的对象做序列化时,大多数序列化库会跳过该键或直接报错。Infinity 同样会被拒绝。
GBK 编码:中文环境下的高频陷阱
上面 5 个原因之外,GBK 编码是中文开发者会额外遇到的一个坑。
Windows 下不少程序——记事本、Excel、旧版日志工具——保存文本文件时默认用 GBK 而不是 UTF-8。如果你的 JSON 文件用 GBK 编码保存,然后被 UTF-8 解码器读取,中文字符会变成乱码,解析器直接报错。
举个例子,汉字"名"在 GBK 里是 2 个字节:0xC3 0xC7。UTF-8 解码器看到 0xC3,会认为这是一个 2 字节 UTF-8 字符的开头,期待第二个字节在 0x80–0xBF 范围内。0xC7 不在范围内,解码直接失败。
Python 里最典型的报错:
import json
with open("config.json", encoding="utf-8") as f:
data = json.load(f)
如果 config.json 是 GBK 编码的,open() 这一步就会抛出:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 3: invalid continuation byte
注意,报错发生在 open() 读取阶段,还没到 json.loads()。
解决方法是显式指定编码:
with open("config.json", encoding="gbk") as f:
data = json.load(f)
如果不确定文件是什么编码,也可以先用 iconv 转成 UTF-8 再解析(Linux 自带 iconv;macOS 通过 Homebrew 安装;Windows 需额外安装):
iconv -f GBK -t UTF-8 config.json > config_utf8.json
Node.js 里情况类似,fs.readFileSync('config.json', 'utf-8') 默认按 UTF-8 解码。GBK 文件同样会乱码,需要用 iconv-lite 之类的库先转码。
两个补充说明:
- 纯 ASCII 的 JSON(只有英文字母、数字、标点)在 GBK 和 UTF-8 下字节完全一样,不会出现这个问题。但只要包含中文字符,GBK 文件在 UTF-8 解码器下就会出错。
- 有些 Windows 程序保存的是"带 BOM 的 UTF-8",开头的
EF BB BF三个字节会导致Unexpected token报错。这和 GBK 是不同的问题,但症状看起来很像,排查时两个都要检查。
按症状找原因:token u / token o / end of input
报错信息里提到的那个字符,往往能直接帮你缩小范围。下面 3 个变体都不是 JSON 文件内容的问题,而是"传给解析器之前"就出了问题。另外,如果看到的是 Unexpected token <,说明返回的不是 JSON 而是 HTML(参见后文环境差异表)。
Unexpected token u in JSON at position 0
JSON.parse 收到了字符串 "undefined"。u 是这个字符串的第一个字符。这几乎可以确定是你要解析的值在那个时候就是 undefined。
const raw = localStorage.getItem("settings"); // 键不存在时返回 null
JSON.parse(undefined); // 报错: "undefined" is not valid JSON
解析之前先确认值是否存在。API 响应体为空、存储键不存在、变量名拼错,是最常见的三个原因。
Unexpected token o in JSON at position 1
解析器收到了 "[object Object]" 这个字符串。position 0 的 [ 看起来像数组开头,所以旧版引擎报告在 position 1 的 o 处失败。把 JavaScript 对象直接传给 JSON.parse 时,隐式字符串转换就会产生这个结果。
const data = { name: "Alice" };
JSON.parse(data); // data 被转成 "[object Object]",报错
这个值已经解析过了,直接用就行。如果目的是深拷贝,用 structuredClone(data),不要走 stringify / parse 来回转。
Unexpected end of JSON input
JSON 还没读完,字符串就结束了。常见情况有两个:空字符串(JSON.parse(""))和被截断的响应(网络请求中断、文件只写了一半)。先打印原始字符串的长度。长度为 0 的话,问题不在 JSON 语法,而在传给解析器之前的环节。
不同环境的报错差异
同一个语法错误,在不同运行环境里的报错措辞不一样。整理一下常见环境的报错格式,方便搜索时对上号:
| 环境 / 运行时 | 典型报错 | 能看出什么 |
|---|---|---|
| 浏览器(Chrome / Node.js) | Unexpected token < in JSON at position 0 | 开头的 < 说明返回的不是 JSON 而是 HTML 错误页。去网络面板看实际响应 |
| Node.js | Unexpected token } in JSON at position 142 | 可以通过 position 数字定位 |
| Python | json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) | 行列号都有。空响应或 HTML 响应传给 json.loads() 时常见 |
| Java(Jackson) | JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name | 同时说明期望的字符和实际的字符 |
浏览器里如果看到 Unexpected token < in JSON at position 0,几乎可以肯定 API 返回了 HTML 错误页(404 或 500 页面),而不是 JSON。这时候应该怀疑请求 URL 和状态码,而不是 JSON 语法。
Python 里的 Expecting value: line 1 column 1 (char 0) 也是同一类问题。响应体为空、或者把非 JSON 内容传给了 json.loads()。最快的排查方式:先把原始响应打印出来看看。
调试步骤
报错只有几行的话肉眼就能修。API 响应或大文件则需要一个固定的排查顺序:
- 粘贴到 JSON 格式化工具,看报错行号和内容
- 往报错位置的前几行看。实际错误经常出现在报错位置之前的行(比如第 10 行漏了逗号,报错出现在第 11 行)
- 对照上面 5 个原因(尾逗号、单引号、无引号键、注释、BOM)逐一排查
- 如果是代码生成的 JSON,检查序列化部分。用字符串拼接手动构建 JSON 是最容易出 bug 的地方
- 如果是从 API 接收的 JSON,解析前先确认编码(特别是 GBK 问题)
用 fetch 接收响应时,先拿原始文本和状态码,再解析。这样可以一个地方就区分出 HTML 错误页、空响应、截断响应三种情况:
const response = await fetch("/api/data");
const raw = await response.text(); // 不用 response.json(),先拿原始文本
if (!response.ok) {
console.error(`HTTP ${response.status}:`, raw.slice(0, 200));
} else if (!raw) {
console.error("响应体为空"); // Unexpected end of JSON input 的典型原因
} else {
try {
const data = JSON.parse(raw);
} catch (e) {
console.error("JSON 解析失败:", e.message, raw.slice(0, 200));
}
}
用 FormatArc 定位错误位置
知道 5 个原因之后,几百行的 JSON 文件里找到具体出错位置仍然是件麻烦事。用 JSON 格式化工具,粘贴进去就能看到报错行号和具体内容。
操作分 3 步:
- 打开 JSON 格式化工具
- 把 JSON 粘贴到左侧编辑器
- 点运行按钮
合法 JSON 会在右侧显示格式化结果。有语法错误的话,报错信息里会带行号,修好那一行再运行一次就行。
所有处理都在浏览器里完成,不会把数据上传到服务器。包含 API 密钥或个人信息的 JSON 也可以放心使用。
预防 JSON 报错
与其报错之后再修,不如养成几个习惯:
- 生成 JSON 时用
JSON.stringify()(各语言的对应序列化函数),不要用字符串拼接 - 编辑器保存时开启 JSON 校验(VS Code、IntelliJ、Sublime Text 都支持)
- CI 里加一步校验。
python -m json.tool < config.json这种简单检查就能在部署前发现坏文件 - 手动编辑时用支持实时校验的工具
另外,不要往不可信的在线工具里粘贴工作数据。怎么判断一个在线工具是否安全,见 在线工具安全检测。FormatArc 所有处理在浏览器内完成,不向外部传输数据。
JSON 不适合的场景
想写注释、想处理多行文本、想管理复杂嵌套配置——如果经常被 JSON 的约束卡住,可以考虑 YAML。YAML 支持注释,多行文本处理也更自然,作为配置文件通常更好读。
YAML 也有自己的坑:对缩进敏感,隐式类型转换等行为不太直观。两个格式需要时用 YAML JSON 转换工具 互相转,按场景分开用就行。
总结
JSON parse 报错的大部分场景都能归到上面 5 类模式:尾逗号、单引号、无引号键、注释、BOM。报错信息里的 Unexpected token 和 position 是排查线索,但文件大的时候交给工具更高效。JSON 的基本语法想回顾的话,看 JSON 语法完全指南;JSON 的格式化方法见 JSON 排版工具。
CSV 数据需要转成 JSON 的话,可以用 CSV JSON 转换工具。

