什么是 JSON?新手完整指南
全面了解 JSON——驱动现代网络的数据交换格式——从语法到在 API 中的使用。
JSON在30秒内
JSON(JavaScript对象表示法)是一种轻量级的基于文本的数据格式,已成为网络数据交换的通用语言。如果您曾经调用过REST API,打开过package.json文件,或使用过MongoDB,那么您已经使用过JSON——可能并没有多想。
从本质上讲,JSON只是将结构化数据表示为纯文本的一种方式。它是人类可读的(大多数情况下),机器可解析的(非常容易),并且几乎所有编程语言都支持它。这种组合正是它获胜的原因。
简要历史
JSON在2000年代初由Douglas Crockford推广。他并没有真正“发明”它——其语法是自1999年以来就存在的JavaScript的一个子集——但他给它起了个名字,建立了一个网站(json.org)和一个规范。有时候,品牌比发明更重要。
该格式在RFC 4627(2006年)中被正式规定,后来被RFC 8259(2017年)和ECMA-404标准所取代。但老实说,自始至今,规范并没有太大变化。JSON的简单性是其最大的优势——没有太多需要更改的内容。
在JSON崛起之前,XML是主导的数据交换格式。如果您想看看为什么开发者纷纷转向JSON,请查看我们的JSON与XML比较。
6种JSON数据类型
JSON支持恰好六种数据类型。没有更多,也没有更少。这种约束是故意的——它保持了跨语言的简单性和互操作性。
| 类型 | 描述 | 示例 |
|---|---|---|
| 字符串 | 用双引号括起来的文本 | "hello world" |
| 数字 | 整数或浮点数(不支持十六进制,不支持NaN,不支持Infinity) | 42, 3.14, -7 |
| 布尔值 | 字面量true或false | true |
| 空值 | 表示空值或未知值 | null |
| 对象 | 无序的键值对集合 | {"name": "Ada"} |
| 数组 | 有序的值列表 | [1, 2, 3] |
注意列表中没有的内容:日期、函数、未定义、注释或二进制数据。JSON本身不支持这些。日期通常作为ISO 8601字符串("2025-01-15T10:30:00Z")传递,二进制数据则进行Base64编码。这有点烦人,但它保持了格式的通用性。
JSON语法规则
JSON的语法是严格的。来自JavaScript的您可能会在这些规则中绊倒。以下是它们,没有任何修饰:
- 键必须是双引号字符串。 单引号无效。未加引号的键无效。这不是JavaScript。
- 字符串必须使用双引号。
'hello'是无效的JSON。"hello"是有效的。 - 没有尾随逗号。 最后一个属性后的逗号?JSON说不。
- 没有注释。 这可能是JSON最具争议的设计决策。Douglas Crockford故意删除了它们以防止滥用。
- 没有单值根(在旧规范中)。 RFC 8259现在允许任何JSON值作为根,但许多解析器仍然期望一个对象或数组。
这是一个有效的JSON文档:
{
"name": "Grace Hopper",
"age": 85,
"languages": ["COBOL", "FORTRAN"],
"retired": true,
"spouse": null,
"address": {
"city": "Arlington",
"state": "VA"
}
}
而这是破坏它的内容:
{
name: "Grace Hopper", // ❌ 未加引号的键
'age': 85, // ❌ 单引号键
"languages": ["COBOL",], // ❌ 尾随逗号
// 这是一个注释 // ❌ 不允许注释
}
有无效的JSON吗? 将其粘贴到我们的JSON格式化工具中,以立即发现语法错误并自动修复常见问题,如尾随逗号。
JSON与JavaScript对象
这几乎让每个JavaScript开发者在某个时候都绊倒。JSON看起来像一个JS对象字面量,但它们并不是同一回事。它们的不同之处在于:
| 特性 | JSON | JavaScript对象 |
|---|---|---|
| 键 | 必须是双引号字符串 | 可以是未加引号的标识符、符号或计算得出的 |
| 字符串 | 仅限双引号 | 单引号、双引号或反引号 |
| 尾随逗号 | 不允许 | 允许 |
| 注释 | 不允许 | 允许 |
| 值 | 字符串、数字、布尔值、空值、对象、数组 | 所有上述内容加上函数、未定义、日期、正则表达式等。 |
| 方法 | 不支持 | 支持 |
| 用途 | 数据交换格式(文本) | 内存中的数据结构 |
在实践中,您可以通过JSON.parse()和JSON.stringify()在它们之间转换:
// 字符串 → 对象
const data = JSON.parse('{"name": "Ada", "year": 1843}');
console.log(data.name); // "Ada"
// 对象 → 字符串
const json = JSON.stringify({ name: "Ada", year: 1843 });
console.log(json); // '{"name":"Ada","year":1843}'
// 使用2个空格缩进进行美化打印
const pretty = JSON.stringify(data, null, 2);
常见用例
1. REST APIs
这是JSON的主战场。现代网络API绝大多数发送和接收JSON。当您从服务器fetch()数据时,您几乎肯定会收到JSON:
const response = await fetch("https://api.example.com/users/1");
const user = await response.json();
// { "id": 1, "name": "Alice", "email": "alice@example.com" }
2. 配置文件
package.json、tsconfig.json、.eslintrc.json、composer.json——列表还在继续。JSON在开发工具中无处不在。缺乏注释在这里确实很痛苦,这就是为什么一些工具支持JSON5或JSONC(带注释的JSON)作为替代方案。
3. NoSQL数据库
MongoDB将文档存储为BSON(二进制JSON)。CouchDB使用纯JSON。DynamoDB、Firestore和无数其他数据库使用类似JSON的结构。如果您在使用文档数据库,那么您正在使用JSON。
4. 本地存储和状态管理
浏览器的localStorage仅存储字符串,因此将状态序列化为JSON是标准方法:
// 保存
localStorage.setItem("prefs", JSON.stringify({ theme: "dark", lang: "en" }));
// 加载
const prefs = JSON.parse(localStorage.getItem("prefs") ?? "{}");
5. 微服务之间的数据交换
消息队列(RabbitMQ、Kafka)、Webhook和服务间通信都严重依赖JSON。在高吞吐量场景中,它并不总是最有效的选择(Protobuf和MessagePack更快),但它是最易于调试的。
JSON最佳实践
经过多年每天与JSON打交道,以下是我推荐的习惯:
- 使用一致的命名约定。 选择
camelCase或snake_case作为键,并在整个API中坚持使用。混合使用会快速导致错误。 - 尽早验证。 不要盲目相信传入的JSON。使用JSON Schema验证在数据到达业务逻辑之前捕获格式错误的数据。
- 保持嵌套浅。 如果您的JSON嵌套超过3-4层,请考虑将其扁平化。深度嵌套的结构难以查询、难以阅读且难以比较。
- 使用数组表示列表,使用对象表示记录。 听起来显而易见,但我见过有人使用带有数字字符串键的对象(
{"0": "a", "1": "b"})而不是数组。不要这样做。 - 在字段没有值时更倾向于使用
null而不是缺失的键。 这使得模式更明确,并避免了关于字段是否被故意省略的模糊性。 - 在开发期间为人类格式化。 压缩的JSON节省字节,但降低可读性。在调试时使用美化打印,在发布时进行压缩。我们的JSON压缩工具处理后者。
自己试试: 将任何JSON粘贴到我们的JSON格式化工具中,以立即验证、格式化并通过语法高亮和树视图探索其结构。
JSON5和JSONC怎么样?
如果JSON的严格性让您感到困扰,您并不孤单。两个流行的扩展放宽了规则:
- JSON5 允许单引号字符串、尾随逗号、注释、未加引号的键等。它非常适合以人为主要受众的配置文件。
- JSONC(带注释的JSON)是VS Code和TypeScript配置使用的最小扩展。它仅添加了注释支持——没有其他内容。
不过,这两者都不是有效的JSON。如果您正在构建API或在系统之间交换数据,请坚持使用标准JSON。仅在工具明确支持的地方使用扩展。
总结
JSON的成功并不是因为它完美,而是因为它足够好,几乎适用于所有场景,并且易于使用。其有限的类型系统有时令人沮丧(我的日期在哪里?),而不允许注释的规则确实是一个痛点。但这些约束也是使其具有普遍互操作性的原因。
如果您刚刚开始,学习JSON的最佳方式是亲手操作。尝试将一些数据粘贴到我们的工具中,故意破坏一些东西,看看会发生什么。理解错误消息是战斗的一半。
继续探索: