简短回答: JSON 比 JavaScript 对象字面量严格得多。绝大多数报错都来自少数几种写法:最后一项后面多了逗号、用了单引号、键没加双引号、带了注释、出现 NaN 或 undefined、文件开头有 BOM、从文档或微信复制来的弯引号、字符串里直接换行、数字以 0 开头、两项之间少了逗号。先看报错里的位置(行和列),在那里或它的前一两个字符找问题。
下面的报错信息都是我用 Node v25.8.0 的 JSON.parse 实际跑出来的。不同引擎措辞不一样:Chrome 和 Node 用的是 V8,这个版本会写"Expected property name or '}'"这类带原因的话;Firefox、Safari 等其他引擎,以及旧版本的 Node,措辞和位置的写法都可能不同(我只在 Node v25.8.0 上跑过,没有逐个验证其他引擎)。所以别按字面去搜,认原因就行。
想直接定位,可以把内容贴进 JSON 格式化工具:它会写出"第几行,第几列",并给出最可能的改法,整个过程在浏览器里完成,内容不上传。
1. 最后一项后面多了逗号
{
"name": "a",
"age": 3,
}
Node 报错:Expected double-quoted property name in JSON at position 29 (line 4 column 1)。数组里写 [1,2,] 则是 Unexpected token ']', "[1,2,]" is not valid JSON。位置指向的是后面的 } 或 ],不是那个逗号。修法:删掉右括号前的逗号。JavaScript 允许这样写,JSON 不允许。
2. 单引号
{'name': 'a'}
Node:Expected property name or '}' in JSON at position 1 (line 1 column 2)。修法:键和文字都换成双引号。
3. 键没加双引号
{name: "a"}
Node 的报错和上一条完全一样,位置也是第 1 行第 2 列。修法:写成 {"name": "a"}。
4. 带了注释
{
// 用户名
"name": "a"
}
Node:Expected property name or '}' in JSON at position 4 (line 2 column 3)。JSON 没有注释语法。修法:删掉注释;要留备注,就加一个普通的键,比如 "_note"。(tsconfig.json 这类能写注释的文件用的是 JSONC,不是标准 JSON。)
5. NaN、undefined
{"score": NaN}
Node:Unexpected token 'N', "{"score": NaN}" is not valid JSON。undefined 同理,只是把 'N' 换成 'u'。JSON 里只有 true、false、null 三个词。修法:用 null,或者写成字符串 "NaN"。
6. 文件开头有 BOM
BOM 是看不见的字节顺序标记(U+FEFF)。我用 "{\"a\":1}" 测试,Node 报:Unexpected token '', "{"a":1}" is not valid JSON。引号里那个字符你看不到,这正是它难查的原因。常见于 Windows 记事本或 Excel 另存的文件。修法:另存为"UTF-8 无 BOM",或删掉第一个字符。格式化工具会把它归为 BOM 并指向第 1 行第 1 列。
7. 从文档或微信复制来的弯引号
{“name”: “a”}
这里的 “ ” 是中文弯引号,看着像引号,但不是 JSON 的 "。Node 的报错和单引号一模一样:Expected property name or '}' in JSON at position 1 (line 1 column 2)。修法:全部换成英文直引号。这个工具对它只会说"出现了意料之外的字符 “",不会特意提示"这是弯引号",所以报错位置是第 1 行第 2 列时,先看看那个字符是不是弯引号。
8. 字符串里直接换行
{"text": "第一行
第二行"}
Node:Bad control character in string literal in JSON at position 13 (line 1 column 14)。修法:字符串里的换行写成 \n,制表符写成 \t。
9. 数字以 0 开头
{"zip": 01234}
Node:Unexpected number in JSON at position 9 (line 1 column 10)。位置指向 0 后面的那个 1。JSON 没有补零的数字。邮编、工号这类要保留前导零的,写成字符串 "01234"。
10. 两项之间少了逗号
{"a":1 "b":2}
Node:Expected ',' or '}' after property value in JSON at position 7 (line 1 column 8)。修法:补逗号。多行文件里这种错常出现在你删掉一行或粘贴拼接之后。
不报错,但会出问题的两种情况
重复的键
{
"id": 1,
"id": 2
}
JSON.parse 不报错,结果是 { id: 2 },后面的覆盖前面的。别的语言和库的做法不一定相同,所以不要依赖它。格式化工具不会删掉任何一个,而是把两个都保留,并列出重复的键在第几行第几列(上例是第 3 行第 3 列)。
超过 JavaScript 精度的数字
JSON.stringify(JSON.parse('{"id": 12345678901234567890}'))
// '{"id":12345678901234567000}'
JavaScript 的数字是双精度浮点数,大于 9,007,199,254,740,991 的整数不能全部精确表示。JSON.parse("9007199254740993") 在 Node 里得到的是 9007199254740992。这不是语法错误,数据却已经变了。格式化工具默认"数字保持原样",上面的 ID 会原样输出 12345678901234567890,并提示这个数字 JavaScript 读取时会被四舍五入;把这个选项关掉后,输出会变成 12345678901234567000。长 ID 最稳的办法是让接口返回字符串。
为什么同一个错,报错位置有时偏了
解析器是从左往右读的,它在"读不下去"的那一刻才报错。所以尾逗号会指向后面的右括号,少逗号会指向下一项的开头。这也是为什么上面几条都建议先看位置前面的字符:真正写错的地方,常常就在报错位置的前一两个字符里。遇到几百行的接口返回,不要靠肉眼逐行找,先让工具给出行列号,再回到源头修。改完重新检查一遍,因为一个错误修好以后,后面可能还藏着第二个。
一个排查顺序
- 看报错位置,到那一行那一列。Node 的
position从 0 数起(上面position 1对应第 1 行第 2 列),有括号里的行列号时直接用行列号。 - 先看该位置前一个字符,尾逗号和少逗号的真正原因往往在前面。
- 位置上的字符"看起来对",就怀疑弯引号、BOM、全角冒号和逗号。
- 还不行,把整段贴进格式化工具,它会按标准(RFC 8259)逐项检查。
如果你的数据其实是表格,可以用 CSV 与 JSON 互转,免得手写 JSON 出错。
常见问题
Unexpected token 是什么意思?
意思是解析器在这个位置遇到了不该出现的字符。它只告诉你"哪里不对",不告诉你原因。对照上面 10 种情况,从该位置和它前一个字符找起。
为什么我的 JS 对象能用,JSON 却报错?
JavaScript 对象字面量允许尾逗号、单引号、不加引号的键和注释,JSON 全都不允许。JSON 是一种数据格式,规则比 JavaScript 语法严格。
JSON 里能写注释吗?
标准 JSON 不能。需要注释的配置文件,可以改用 JSONC、YAML,或者在 JSON 里加 "_note" 这样的普通字段。
格式化 JSON 时长数字会变吗?
用 JSON.parse 再 JSON.stringify 会变,超过 9,007,199,254,740,991 的整数会被改写。这个工具默认保留原来的每一位,同时提示哪些数字用 JavaScript 读会丢精度。