FlowingDev

JSON 详解:默默驱动着整个网络的数据格式

学习什么是 JSON (JavaScript Object Notation),为什么它能成为主流的数据交换格式,以及它那简单的文本结构是如何工作的。

试用工具: JSON 编辑器

一句话概括

JSON 是一种轻量的、基于文本的格式,用于组织和交换数据,它既方便人类阅读,也易于机器解析。

它解决了什么问题

在互联网的洪荒年代(大概是 90 年代末到 2000 年初),如果你想让网站不刷新整个页面的情况下获取新数据,你很可能在用一种叫做 AJAX(Asynchronous JavaScript and XML)的技术。顾名思义,当时的首选数据格式就是 XML。

XML 功能很强大,但它也……相当啰嗦。又笨重。到处都是开始标签、结束标签、属性和命名空间。用浏览器的御用语言 JavaScript 来解析它简直是个苦差事。你得在一个笨拙的文档树里导航,感觉一点儿也不原生。

<!-- 这只是一个用户。想象一下一个包含数千用户的列表。我的天。 -->
<user id="123">
  <username>coder_dave</username>
  <isActive>true</isActive>
  <roles>
    <role>admin</role>
    <role>editor</role>
  </roles>
</user>

大约在 2001 年,一位名叫 Douglas Crockford 的开发者在做一个项目时,需要一个更简单的方法来向浏览器传递数据。他灵光一闪,想到了一个绝妙的主意:JavaScript 本身不就有表示数据结构的完美方式——它自己的对象字面量语法吗?那要是能把数据作为看起来像 JavaScript 对象的字符串来发送,会怎么样呢?

JSON(JavaScript Object Notation)就此诞生。服务器可以发送这样的文本:

{
  "id": 123,
  "username": "coder_dave",
  "isActive": true,
  "roles": ["admin", "editor"]
}

……然后浏览器就能毫不费力地把它转换成一个可以立即使用的原生 JavaScript 对象。它既精简又干净,完美契合了 Web 的“母语”。这种简洁性引发了 Web API 的寒武纪大爆发,推动了动态单页应用、移动后端的崛起,基本上也成就了我们今天所知的整个现代互联网。JSON 不仅仅是解决了一个技术问题,它为全新一代的软件铺平了道路。

底层工作原理

究其核心,JSON 只是一套用文本记录数据的规则。规则很简单,而这恰恰是它如此成功的原因。

构建基块:键和值

整个 JSON 的世界都构建在一个简单的结构上:键值对。

"key": value

  • 键(key) 永远是字符串,并且必须用双引号括起来。这点没得商量。
  • 值(value) 则可以是几种特定数据类型之一。

这个键值对描述了一条信息,比如 "name": "Luke Skywalker" 或者 "age": 19。

数据类型

JSON 对值的类型要求很严格。你不能随便往里扔东西。你只能用六种基本类型外加一个 null。

类型 示例 描述
字符串 "The force is strong with this one." 任意文本。必须用双引号。
数字 1138 或 3.14 整数或浮点数。不做区分。
布尔值 true 或 false 永远是小写。代表二进制状态。
数组 ["Tatooine", "Dagobah", "Bespin"] 一个有序的值列表,用 [] 括起来。
对象 { "weapon": "lightsaber", "color": "green" } 一个无序的键值对集合,用 {} 括起来。
Null null 代表“有意地没有值”。

就这些。注意看少了什么:函数、日期(日期通常作为字符串发送)、undefined,以及最著名的——注释。这种极简主义是特性,不是 bug;它使得格式清晰明确,任何编程语言都能轻松解析。

组织数据:对象和数组

真正的威力来自于嵌套这些类型。键值对中的 value 可以是另一个对象或数组。这让你能构建出任意复杂的数据结构。

对象({...})用于将关于单个“事物”的相关数据组合在一起。可以把它想象成一个词典条目或者一份个人资料。

数组([...])用于有序的项目列表。数组中的项目可以是任何类型,甚至是混合类型(尽管通常好的做法是保持类型统一)。

我们来看一个更完整的例子:

{
  "squadName": "Star Wars Heroes",
  "formed": 1977,
  "active": true,
  "members": [
    {
      "name": "Luke Skywalker",
      "age": 19,
      "secretIdentity": null,
      "powers": [
        "Jedi mind tricks",
        "Piloting",
        "The Force"
      ]
    },
    {
      "name": "Han Solo",
      "age": 29,
      "secretIdentity": null,
      "powers": [
        "Blaster accuracy",
        "Sarcasm",
        "Kessel Run under 12 parsecs"
      ]
    }
  ]
}

看到了吗?我们有一个描述小队的外层对象。它的一个属性 "members" 是一个数组。该数组的每个元素是另一个对象,代表一个英雄。每个英雄对象又有自己的属性,其中一个("powers")是另一个字符串数组。这就是用 JSON 构建数据树的方式。

从文本到对象:解析(Parsing)

魔法就在于如何把这段文本变成程序可用的东西。

  1. 序列化 (Serialization): 一个服务器端应用(用 Python、Java、Go 等语言编写)在内存中有一些数据。它使用一个 JSON 库将这些数据序列化(serialize)成一个 JSON 格式的字符串。在 JavaScript 中,这个操作是 JSON.stringify()。
  2. 传输 (Transmission): 这个字符串通过网络发送出去,通常作为 HTTP 响应的主体(body)。
  3. 解析 (Parsing): 客户端(比如 web 浏览器)接收到这个字符串。它使用一个内置的 JSON 解析器(parser)将字符串转回它能操作的原生数据结构。在 JavaScript 中,这个操作是 JSON.parse()。

这个“序列化-解析”的两步过程,正是现代网络通信的心跳。

真实世界的案例

爱出 Bug 的移动 App

一家初创公司为一家本地连锁餐厅推出了一个移动点餐 App。一天早上,这个 App 开始在所有用户打开时崩溃。开发团队顿时陷入一片恐慌。服务器日志显示 API 返回了 200 OK 状态,数据乍一看也没什么问题。经过数小时的疯狂调试,他们终于检查了 API 响应的原始文本。原来一位后端开发为了方便同事,在生成 JSON 的代码里直接加了条注释:// TODO: Confirm weekend hours。这条注释被包含在了最终的 JSON 字符串里。虽然在代码里无害,但注释会使 JSON 变得无效。App 的严格 JSON 解析器看到意料之外的 // 后立即报错,导致 App 甚至来不及显示错误信息就崩溃了。

教训: JSON 解析器可不宽容。这个格式被严格定义是有原因的:保证互操作性。一个无效字符——一个注释、一个末尾的逗号、一个单引号——都会导致合规的解析器拒绝整个数据包。

国际化定价惨案

一家美国电商公司正准备扩展到德国市场。他们的 API 以 JSON 格式发送产品信息,其中包含一个 price 字段。对于一件 19.95 美元的商品,JSON 是 "price": 19.95。为了准备德国站的上线,一位开发更新了后端,让价格按照德国的习惯格式化,也就是用逗号作为小数点分隔符。于是 API 开始发送 "price": "19,95"。然而,前端代码仍然期望得到一个数字。在 JavaScript 中,parseFloat("19,95") 的计算结果是 19,逗号后面的所有内容都被丢掉了。一夜之间,德国站的所有商品都以一个巨大的、错误的折扣价显示了出来。

教训: JSON 是用来传输原始数据的,不是用来做展示的。数据类型至关重要。价格是一个数字,就应该作为数字(19.95)发送。让客户端应用(浏览器或移动 App)去负责根据用户的地区设置把这个数字格式化成 $19.95、19,95 € 或 ¥19。不要把数据和显示逻辑混为一谈。

无法解释的配置文件

一个小团队正在用一个 config.json 文件来搭建一个新服务,文件里存着数据库连接字符串、API 密钥和功能开关。随着配置越来越复杂,他们迫切需要添加注释来解释每个神秘的设置是干嘛的,以及为什么设置成某个特定的值。但 JSON 禁止注释。他们的“解决方案”是创建了一个 config_documentation.md 文件,这个文件必须与 config.json 保持同步。这很快就成了一个巨大的麻烦。最终,他们意识到了自己的错误。

教训: 用合适的工具干合适的活。对于机器之间的数据交换(比如 API 响应),JSON 是无可争议的王者。但对于需要人来维护、注释和可读性至关重要的配置文件,像 YAML 甚至一个简单的 .js 文件这样的其他格式通常是更好的选择。

常见错误和陷阱

  • 末尾逗号: 在对象或数组的最后一个元素后面添加逗号("key": "value",})会导致 JSON 无效。对于习惯了 JavaScript 中更宽松语法的开发者来说,这是一个常见错误。
  • 注释: 不能有注释。// 和 /* ... */ 都不是规范的一部分,会破坏解析。如果你需要添加元数据,你必须在数据结构内部完成,例如:{ "_comment": "这是我的笔记", "realData": "..." }。
  • 单引号: 所有的键和所有的字符串值都必须使用双引号(")。使用单引号(')是无效的 JSON,尽管这在 JavaScript 中很常见。
  • 未定义的键: 发送 {"key": undefined} 是不可能的。在序列化过程中,这个键值对通常会被省略。要表示缺失的值,请使用 null。
  • 将数字用作字符串: 虽然你可以把数字作为字符串发送(例如 "id": "123"),但这是一种不好的做法。它迫使接收方应用程序需要做额外的工作将其转换回数字,并且可能导致一些隐蔽的 bug(例如,在字符串比较中,"10" > "9" 的结果是 false)。

为什么你应该关注它

只要你跟代码打交道,不管以何种方式,你都会遇到 JSON。这不是一个是否会遇到的问题,而是何时以及多频繁遇到的问题。

  • Web 开发者: 你会从 API 中消费 JSON,并从你的表单中发送它。你的整个应用状态很可能就是作为一个类似 JSON 的对象来管理的。
  • 后端开发者: 你会构建产出 JSON 的 API,也会消费来自其他服务的 JSON。
  • 移动开发者: 你将完全通过使用 JSON 的 API 与你的后端进行通信。
  • DevOps/SREs: 基础设施即代码(Infrastructure-as-code)工具、CI/CD 流水线以及云服务商的 API,全都是用 JSON 或类似格式来配置和管理的。
  • 数据科学家: 你会从 Web API 中拉取数据,而这些数据几乎总是以 JSON 格式到达。
  • 好奇的非开发者: 了解 JSON 简单的键值结构,可以揭开你手机上的 App 如何获取数据以及网站如何动态加载内容的神秘面纱。这就像是让你一窥数字世界的底层运作。

JSON 是互联网上数据的通用语(lingua franca)。了解它的规则和用途,对于任何构建或使用现代软件的人来说,都是一项基本技能。

深入探索

  • JSON.org: 由 Douglas Crockford 编写的原始单页规范。简约设计的典范之作。(注意:链接已指向中文版)
  • RFC 8259: IETF 官方“标准”,为互联网正式定义了 JSON 格式。
  • MDN: 使用 JSON: 面向 JavaScript 开发者的权威指南,解释了 JSON.parse() 和 JSON.stringify()。(注意:链接已指向中文版)
  • Wikipedia: JSON: 对 JSON 的历史、衍生品(如 GeoJSON)以及与其他格式的比较提供了很好的概述。

理论搞定,动手试试吧——100% 在你的浏览器中运行。

试用工具: JSON 编辑器