← 全部文章

JSON vs JavaScript 对象:为什么不允许单引号

很多开发者把 JS 对象字面量当作 JSON。它们并不一样:单引号、未加引号的键、尾随逗号、undefined、NaN —— 这里把每处区别配示例讲清楚。

你把看上去合法的数据粘到一个 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 JSON

Safari 的错误信息最有帮助 —— 它直接告诉你问题到底是什么。其他几个只是报告那个意外字符。

为什么 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. 没有 NaNInfinity

JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'
// NaN 和 Infinity 变成 null —— 还是悄悄地!

JSON.parse('{"ratio": NaN}');
// → SyntaxError: Unexpected token 'N'

NaNInfinity 是合法的 JavaScript 数字值,但不属于 JSON 数字规范。JSON.stringify() 出去时把它们转成 nullJSON.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 }{}(属性被丢)
0xFF255

把 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、尾随逗号、注释、undefinedNaN、函数和十六进制数字 —— 这些都不是合法 JSON。完整 cheatsheet 在上面,入门见 什么是 JSON?

我怎么把 JavaScript 对象转成 JSON?

JSON.stringify() —— 永远不要手工改引号。它能处理每一种边界情况:丢掉不可序列化的值并正确地转义字符串。

为什么不能用 eval() 解析 JS 对象字面量?

eval() 会执行任意代码,对不可信输入是严重安全风险。改用一个修复 parser(或 JSON Fix)安全地把 JS 对象字面量转成合法 JSON。

立刻修好单引号

如果你手头有一段 JavaScript 对象字面量、现在就要转成合法 JSON,JSON Fix 会自动做这件事。它把单引号转双引号、给裸 key 加引号、去掉尾随逗号,并修上面列出的所有其他差异 —— 都在你的浏览器里跑,不会发到任何服务器。