你把看上去合法的数据粘到一个 JSON parser 里,它立刻抛 SyntaxError: Unexpected token '''。这段数据来自你的 JavaScript 源码,看起来完全合理 —— 但 JSON 和 JavaScript 对象字面量并不是同一种格式,它们的差异比大多数开发者意识到的更重要。
短答案:JSON 不支持单引号
在 JSON 里单引号完全不合法。每一个字符串 —— key 和值都算 —— 都必须用双引号。没有例外。
// ❌ 非法 JSON —— 单引号字符串
{ 'name': 'Ada Lovelace', 'active': true }
// ✅ 合法 JSON
{ "name": "Ada Lovelace", "active": true }你在不同环境里会看到的错误:
// Chrome / Node.js (V8)
SyntaxError: Unexpected token "'", "{'name':..." is not valid JSON
// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data
// Safari
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSONSafari 的错误信息最有帮助 —— 它直接告诉你问题到底是什么。其他几个只是报告那个意外字符。
为什么 JSON 要求双引号?
JSON 由 Douglas Crockford 在 2001 年定义,标准化为 RFC 8259。语法明确规定只有一种字符串定界符:双引号字符(U+0022)。单引号根本不在语法里。
原因是简单。JSON 是一种数据交换格式,目的是被任何语言解析,而不只是 JavaScript。规范强制一种唯一的引号风格,消除了一整类歧义。Go、Python、Java、Rust 以及任何其他语言的 parser 都实现了完全相同的规则。
JSON vs JavaScript 对象字面量:完整差异
单引号只是若干差异之一。完整版如下:
1. key 必须是双引号字符串
// ❌ JavaScript 对象字面量 —— 裸 key、单引号 key
{ name: "Ada", 'score': 98 }
// ✅ JSON —— 所有 key 都是双引号字符串
{ "name": "Ada", "score": 98 }JavaScript 对象字面量接受裸标识符、单引号字符串和双引号字符串作为 key。JSON 只接受双引号字符串。
2. 不允许尾随逗号
// ❌ 非法 JSON
{ "name": "Ada", "score": 98, }
// ^ 尾随逗号
// ✅ 合法 JSON
{ "name": "Ada", "score": 98 }现代 JavaScript 明确允许尾随逗号。JSON 不允许 —— 见我们关于 JSON 尾随逗号的完整指南。
3. 没有注释
// ❌ 非法 JSON
{
// 用户档案
"name": "Ada"
}
// ✅ JSON 没有注释语法
{ "name": "Ada" }JSON 根本没有任何注释语法。如果你需要在配置文件里写注释,可以考虑 JSONC 格式(JSON-with-Comments),VS Code 设置、TypeScript 的 tsconfig.json 以及一些配置文件 parser 都支持它。
4. 没有 undefined、函数或 Symbol
// JavaScript 对象 —— 接受不可序列化的值
const obj = {
fn: () => 42, // 函数
sym: Symbol('id'), // Symbol
val: undefined, // undefined
};
// JSON.stringify 会悄悄丢掉或转化它们
JSON.stringify(obj);
// → '{}' (这三个属性都没了或变成 null) JSON 恰好支持六种值类型:string、number、boolean(true/false)、null、object 和 array。函数、Symbol 和 undefined 在 JSON 里没有任何表示。JSON.stringify() 会悄悄丢掉对象里这些值的属性,并把它们在数组里转成 null。
5. 没有 NaN 或 Infinity
JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'
// NaN 和 Infinity 变成 null —— 还是悄悄地!
JSON.parse('{"ratio": NaN}');
// → SyntaxError: Unexpected token 'N'NaN 和 Infinity 是合法的 JavaScript 数字值,但不属于 JSON 数字规范。JSON.stringify() 出去时把它们转成 null;JSON.parse() 进来时把它们拒掉。这种不对称是隐蔽 bug 的常见来源。
6. 数字:没有十六进制、没有前导零、没有 +
// ❌ 非法 JSON
{ "flags": 0xFF, "code": 007, "delta": +1.5 }
// ✅ 合法 JSON
{ "flags": 255, "code": 7, "delta": 1.5 } JSON 数字必须是十进制,不能带显式的 + 号,不能有前导零(除了小数点前那一个 0 数字)。十六进制字面量和八进制字面量都不是合法 JSON。
7. 没有多行字符串
// ❌ 非法 JSON —— 字符串里出现字面换行
{ "bio": "Line one
Line two" }
// ✅ 用转义序列
{ "bio": "Line one\nLine two" }8. 计算键、Symbol 键和方法简写
另外三种看似 JSON 但在 JSON 里没有对应物的 JS 专属特性:
// ❌ 计算属性名 —— 是表达式,不是字面量
const k = "id";
const obj = { [k]: 1, [`user_${k}`]: 1 };
// JSON 没有表达式;key 必须是字面意义上的双引号字符串。
// ❌ Symbol key —— 会被 JSON.stringify 悄悄丢掉
const tag = Symbol('tag');
JSON.stringify({ [tag]: 'admin' }); // → '{}'
// ❌ 方法简写 —— 函数会被丢掉/省略
const obj = { greet() { return 'hi'; } };
JSON.stringify(obj); // → '{}' 如果你需要其中任何一个,就把你的对象序列化成一个 JSON 形状的投影 —— JSON.stringify() 永远只会产出字符串 key 和那六种 JSON 值类型。
一份快速转换 cheatsheet
| JavaScript 对象字面量 | 合法 JSON 等价写法 |
|---|---|
{ name: "Ada" } | {"name":"Ada"} |
{ 'name': 'Ada' } | {"name":"Ada"} |
{ a: 1, } | {"a":1} |
{ val: undefined } | {}(属性被丢) |
{ n: NaN } | {"n":null} |
{ n: Infinity } | {"n":null} |
{ fn: () => 1 } | {}(属性被丢) |
0xFF | 255 |
把 JS 对象转成 JSON 的最稳妥方式
永远不要靠手工编辑引号把 JavaScript 对象转成 JSON。用 JSON.stringify() —— 它把每一种边界情况都处理对:
const obj = { name: 'Ada', score: 98, active: true };
// 紧凑
JSON.stringify(obj);
// → '{"name":"Ada","score":98,"active":true}'
// 带 2 个空格缩进的 pretty-print
JSON.stringify(obj, null, 2);
// → '{
"name": "Ada",
"score": 98,
"active": true
}' JSON.stringify() 的第二个参数是一个 replacer —— 你可以传一个 key 名数组只包含那些属性,或者传一个函数来变换值:
// 只包含指定 key
JSON.stringify(obj, ['name', 'score'], 2);
// → '{
"name": "Ada",
"score": 98
}'
// 自定义 replacer —— 把 undefined 转成 null
JSON.stringify({ a: 1, b: undefined }, (key, value) =>
value === undefined ? null : value
);
// → '{"a":1,"b":null}'拿到的是一段 JS 对象字面量字符串时
有时候你拿到的数据看上去像 JSON,其实是 JavaScript 对象字面量 —— 可能来自日志、调试工具,或者一个没按规范走的 API。这时 JSON.parse() 会失败,你有两个选择:
- 用一个修复 parser —— 把单引号转双引号、去掉尾随逗号、给裸 key 加引号,自动处理上面所有差异。对于不可信输入这是最稳的办法。
- 用
eval()或Function()—— 技术上对合法 JavaScript 能行,但只要数据源不完全可信,这就是严重安全风险。永远不要在用户输入上这么做。
常见问题
JSON 支持单引号吗?
不支持。JSON 语法(RFC 8259)只定义了一种字符串定界符 —— 双引号。key 和值都必须用双引号;单引号会抛 SyntaxError。
JSON 和 JavaScript 对象有什么区别?
JSON 是一种严格的文本格式;JavaScript 对象字面量是宽松的代码。JS 允许单引号、不带引号的 key、尾随逗号、注释、undefined、NaN、函数和十六进制数字 —— 这些都不是合法 JSON。完整 cheatsheet 在上面,入门见 什么是 JSON?
我怎么把 JavaScript 对象转成 JSON?
用 JSON.stringify() —— 永远不要手工改引号。它能处理每一种边界情况:丢掉不可序列化的值并正确地转义字符串。
为什么不能用 eval() 解析 JS 对象字面量?
eval() 会执行任意代码,对不可信输入是严重安全风险。改用一个修复 parser(或 JSON Fix)安全地把 JS 对象字面量转成合法 JSON。
立刻修好单引号
如果你手头有一段 JavaScript 对象字面量、现在就要转成合法 JSON,JSON Fix 会自动做这件事。它把单引号转双引号、给裸 key 加引号、去掉尾随逗号,并修上面列出的所有其他差异 —— 都在你的浏览器里跑,不会发到任何服务器。
- JSON Fix —— 把 JS 对象字面量即时转成合法 JSON
- 修复 JSON 里的单引号 —— 速查指南,含坏例和好例
- JSON vs JavaScript 对象字面量 —— 每一项语法差异以及并排示例
- JSON.parse Unexpected Token 错误 —— SyntaxError 每一种变体的指南
- JSON 里的尾随逗号 —— 为什么 JS 允许但 JSON 禁止