JSON 是 JavaScript Object Notation 的缩写,而这是一个会误导人的名字——它现在和 JavaScript 关系很小,而且什么东西都在用它。它是一种用纯文本写结构化数据的方式,当你跟一个服务要求导出你的账号数据、当一个 App 保存它的设置、当一个程序把数据送给另一个程序,拿到的就是它。
一个小例子几乎包含了这个格式能做的全部:
{
"name": "Widget",
"price": 4.50,
"inStock": true,
"tags": ["small", "blue"],
"discount": null
}
大括号装着一个对象——一组有名字的值。方括号装着一个数组——一个有顺序的列表。而整个类型系统就是这些:对象、数组、字符串、数字、true/false,以及 null。六样东西。
对象和数组可以互相包含到任意深度,JSON 就是这样用这么小的词汇量描述复杂结构的。
JSON 严格得会让任何写过真正编程语言的人意外,因为它长得像好几种语言,却禁止那些语言允许的事。
'name' 是无效的。每一个字符串和每一个键都要双引号。name: "Widget" 无效;"name": "Widget" 才对。// 不行,/* */ 也不行。这是这个格式历史上被抱怨最多的缺席,而它是刻意的:注释会诱使人把意义放在解析器会忽略的地方。注释禁令有一个通用的变通做法:加一个普通的键,比如 "_comment",把你的备注当成它的值。那不是注释——程序会把它当成数据读——但那就是大家在配置文件里放说明的位置。
这件事值得理解,因为它会让错误消息没那么令人挫折。
JSON 没有任何从语法错误中恢复的办法。它不像 CSV 有“行”这种记录边界,所以一个碰到意外内容的解析器没办法跳过坏掉的部分继续——整个文件就是失败。
更糟的是,报出来的行号是解析器察觉的地方,不是你犯错的地方。在靠前的位置漏掉一个右大括号,错误会出现在文件的最尾端,因为那才是结构第一次变得不可能的点。漏掉一个逗号,消息常常指向你要修的那一行的下一行。
所以当一个 JSON 错误指出某一行,去看它上面的结构,而不是那一行本身。一个验证器、或者一个会标示成对括号的编辑器,找这些东西比用读的快得多。
两者都是纯文本,两者都在描述结构。JSON 在数据交换上赢在两点。
第一是重量。XML 里一个值要被一个开标签和一个闭标签包起来;JSON 里它是一个名字、一个冒号和那个值。在一个大文件上,那在体积和可读性上都是相当大的差别。
第二点更要紧:JSON 的六种类型直接对应到每一种编程语言本来就有的数据结构。对象变成字典、数组变成列表、数字就是数字。XML 需要解读——在某个东西决定它是什么意思之前,一切都是文本。
JSON 换来这些所放弃的,是所有和文档有关的能力:没有注释、没有属性、没有命名空间,也没有办法表示“文本里面夹着标记”。这就是为什么数据以 JSON 流通,而文档留在 XML。
这是常见的实际任务,而它行不行得通,完全取决于你数据的形状。
一个扁平的对象数组转得很干净。一串字段都相同的记录就是一张表:一个对象一行,一个键一列。任何转换工具都处理得了。
嵌套数据不行。如果每一条记录里面含有另一组对象的数组——一张有好几个商品的订单、一个有多个地址的用户——没有诚实的方法把那个放进一个格子里。转换要么把它摊平成 items.0.name、items.1.name 这样的列,要么把嵌套结构整个丢掉。
这不是转换器的限制。电子表格是一个长方形而 JSON 是一棵树,树塞不进长方形。当数据真的是嵌套的,有用的做法通常是把你实际需要的那一部分抽出来成为一个扁平列表,而不是试图转换整个结构。
一个大型导出文件往往不是一份 JSON 文档,而是一个每行一个完整 JSON 对象的文件,叫做 JSONL 或 NDJSON。
理由很实际:一个巨大的单一 JSON 数组必须整个读进内存才能做任何事,而且不重写整个文件就没办法往后追加。一行一个对象可以一行一行处理,而且可以永远追加下去。
如果一个文件是 .jsonl 或 .ndjson,或者打开是很多行、每行都以 { 开头,那就是这种东西——而把它丢给一般的 JSON 解析器会失败,因为整个文件作为一个整体并不是合法的 JSON。
关于这一点,Markdown 成立的每一件事在这里也成立。JSON 在任何文本编辑器里都打得开、任何软件消失它都活得下来、用一般工具就搜得到,而且两个版本之间可以逐行比较。
唯一的实务步骤是在阅读前把它排版美化。导出的 JSON 常常是单一一条巨大的行。任何格式化工具或堪用的编辑器都会把它重新缩进成人看得懂的样子,而不改动数据。
"_comment" 键