JSON Key 排序、压缩与规范化(canonical JSON)

同样内容的 JSON,键顺序不同就是不同的字符串,这会给缓存、签名与 Diff 带来麻烦。规范化(canonical)就是把表示收敛成唯一形式。 本文讲清 key 排序的规则、数组为何不动、压缩与 gzip 的区别,示例可在 JSON Key 排序 工具中复现。

一、问题:同样的数据,不同的字符串

下面两份 JSON 语义完全相同,字节却不同,直接做哈希或字符串比较会得出「不相等」:

{ "b": 1, "a": 2 }
{ "a": 2, "b": 1 }

规范化的目标:给定任意键顺序,输出同一个标准字符串。常用手段就是「键排序 + 最小化」。

二、按 key 字典序递归排序

站内排序规则:对每个对象,按其键的字典序排序;嵌套对象递归处理,数组顺序保持不变。例如:

// 输入
{
  "user": { "name": "Ada", "id": 1 },
  "tags": ["b", "a"]
}

// 排序后(对象键按字典序,数组保持 ["b","a"])
{
  "tags": ["b", "a"],
  "user": { "id": 1, "name": "Ada" }
}

关键点:排序是字符串码位比较,不是数值比较。所以键 "10" 会排在 "2" 前面(字符 "1" 的码位小于 "2")。若你的键是数字字符串且需要数值序,应改用等宽补零(如 "002"、"010")或先转换再比。

三、数组为什么不动

数组的顺序承载语义,比如待办列表、日志时间线、坐标点,打乱就改变了含义。因此规范化只重排对象的键,绝不触碰数组元素顺序。 这条约定让 canonical JSON 既保证键顺序无关,又不破坏数据本身。

四、压缩 vs gzip

  • 最小化(minify)。移除 JSON 文本中的空白、缩进与换行,输出仍是纯文本,内容语义不变,只是更短。 适合生产传输与存储。
  • gzip。传输 / 存储层的字节级压缩,对任意内容生效,与是不是 JSON 无关。常见于 HTTP 的 Content-Encoding: gzip。
  • 两者可叠加。先最小化再 gzip,通常能得到最小的字节数;最小化去掉的空白恰好是 gzip 收益较低的部分。

五、canonical JSON 的用途

  • 缓存键。把请求参数排序后序列化作为缓存键,避免键顺序不同导致缓存击穿。
  • 内容指纹 / 去重。排序后哈希得到稳定指纹,内容相同即指纹相同,可用于快照比对与去重。
  • 请求签名。接口签名要求参与签名的 JSON 有唯一表示,否则两端各自序列化的顺序不同会验签失败。
  • 干净的 Diff。对比前先排序,可排除键顺序造成的伪差异,让改动一目了然。

六、常见错误

  • 以为排序会重排数组。数组顺序被保留;需要排序数组请用数组排序,而非 key 排序。
  • 期望数值序。键排序是码位比较,"10" 排在 "2" 前;要数值序请先补零或转数字。
  • 排序对象含重复键的非法 JSON。重复键本就非法,先去重再排序。
  • 把 minify 当加密。最小化只是去空白,内容照样可读,不要用于「隐藏」数据。
  • 忽略数字表示差异。1.0 与 1 在部分语言序列化不同,做指纹时需统一数字格式。

动手跑一遍

把上面两份键顺序不同的 JSON 分别粘进排序工具,观察输出是否一致。再用压缩工具得到最小化文本,或送去 Diff 观察干净对比。

常见问题

为什么排完序后 "10" 跑到了 "2" 前面?

因为 key 排序是字符串的码位(Unicode 码位 / UTF-16 码元)比较,不是数值大小比较。字符 "1" 的码位小于 "2",所以 "10" 排在 "2" 前面。想要数值序需要先把键转成数字再比,但那已不是标准 JSON key 排序。

排序会打乱数组吗?

不会。数组的顺序是数据语义的一部分(如列表、时间序列),排序只递归处理对象的键,数组元素顺序原样保留。这也是规范化 JSON 的通行约定。

压缩和 gzip 是一回事吗?

不是。这里说的压缩是「去掉 JSON 文本里的空白与换行」的最小化,输出仍是纯文本,只是更短;gzip 是传输层的字节级压缩,与 JSON 内容无关。两者可以叠加:先最小化 JSON,再 gzip 传输,效果最好。

canonical JSON 有什么用?

当同一份数据在不同系统里键的顺序不同时,直接哈希会得到不同结果。把键排序后再序列化,可得到稳定一致的字符串,用于缓存键、内容指纹、请求签名、去重与快照比较。顺序无关,内容相同即结果相同。

在哪里能完成排序与压缩?

站内 JSON Key 排序工具按 key 字母序递归排序对象、数组不动;JSON 压缩工具一步移除全部空白。两者都能与对比工具配合:排序后再做 Diff,可排除键顺序造成的噪声。

相关工具