开发者工具 · JSON 格式化程序和验证程序
JSON 中的按键顺序重要吗?排序、相等和 RFC 8785
· 背景
json 标准 验证
JSON 规范调用无序对象,但真正的解析器和序列化器通常保留顺序,并且签名方案依赖于它。这篇文章阐明了规范的内容、实现的作用以及规范化如何解决紧张局势。
相同的数据,不同的字节
`{"city":"Oslo","temp":4}` 和 `{"temp":4,"city":"Oslo"}` 包含相同的两个名称和值,但它们的源字节不同。缩进可以添加更多文本差异,而无需更改任何解析值。这就是为什么“equal JSON”需要一个比较规则:您是在比较文本、解析的对象还是另一个协议定义的规范表示?
ToolAcre 可以通过使用相同的缩进格式化两个文档来消除空白噪音。当选择该选项时,它还可以递归地对对象键进行排序。排序会故意更改成员顺序,但不会移动数组元素,因为数组位置代表数据。普通格式和此可选排序都不会生成 RFC 8785 规范 JSON,因此输出不得替换为指定的签名格式。
RFC 8259 所说的 — 对象是名称 /value 对的无序集合,并且实现可能会暴露顺序或不暴露顺序
RFC 8259 将对象描述为 name/value 对的无序集合。因此,将成员顺序视为普通 JSON 对象含义的软件依赖于该抽象模型之外的行为。数组是显式排序的,因此 `["draft","final"]` 不能与 `["final","draft"]` 互换。对象顺序和数组顺序决不能用相同的规则规范化。
RFC 还指出,库的不同之处在于它们是否向调用者公开成员排序。该警告对于可移植设计来说已经足够了:不要通过将一个对象成员放在另一个对象成员之前来编码优先级或顺序。如果顺序很重要,请用数组或显式字段表示它。显示稳定顺序的格式化程序对人类来说很方便,但它不会将位置转换为对象的标准级属性。
这个格式化程序实际上做了什么
禁用排序后,ToolAcre 会解析文档并序列化生成的 JavaScript 值。输出遵循 JavaScript 属性枚举行为,而不是逐字节保留原始令牌流。大多数普通字符串键以熟悉的顺序出现,而类似整数索引的名称可以在其他名称之前发出。数字拼写和转义选择也可以在重新序列化期间标准化。
启用排序后,格式化程序会构建新对象,其自己的键在每个嵌套对象中按字母顺序排列。对于 `{"z":{"b":1,"a":2},"items":[{"d":4,"c":3},"x"]}`,对象名称变为 `items`、`z`;嵌套对象名称也已排序;并且数组仍然包含 `"x"` 之前的对象。对数组内的对象进行排序并不意味着对数组本身进行排序。
当字节顺序很重要时
只要进程消耗精确的字节而不是抽象值,文本顺序就很重要。当成员移动或空白更改时,文件哈希、缓存密钥、数字签名或基于行的差异会发生变化。这与无序对象模型并不矛盾;这意味着周围的进程选择了字节表示作为其输入的一部分。表示规则必须是明确的和共享的。
对于例行审查,一致的缩进和可选的字母排序可以使更改更容易看到。对于密码学或协议工作来说,“看起来稳定”并不是一个契约。生产者和验证者必须在散列或签名之前使用其协议所需的确切规范化算法。如果没有指定算法,则不要假设 ToolAcre 的输出将与跨版本、运行时或边缘情况值的另一个序列化器相匹配。
为什么键排序不是 RFC 8785
RFC 8785 定义了 JSON 规范化方案,用于从兼容数据生成可重复字节。它的工作范围比按字母顺序放置按键更广泛。它指定确定性属性排序以及字符串和数字的精确序列化行为,并对输入模型施加约束。漂亮的缩进不是规范输出的一部分,并且区域设置感知排序不是可接受的近似值。
ToolAcre 未提出 RFC 8785 声明。它的排序选项是分层于 `JSON.parse` 和 `JSON.stringify` 之上的可读性功能;它不会验证 I-JSON 前提条件或替换 RFC 的序列化规则。诸如 `1e-7` 之类的值、包含非 ASCII 字符的键或转义字符串可以揭示随意排序的格式化程序和一致的规范化程序之间的差异。当需要 JCS 时,使用经过测试的 JCS 实现。
工作示例:公平比较两个文档
将 `{"meta":{"rev":2,"owner":"Mira"},"steps":["cut","pack"]}` 与 `{"steps":["cut","pack"],"meta":{"owner":"Mira","rev":2}}` 进行比较。将两者格式化为两个空格并禁用排序:空格变得一致,但根和嵌套成员顺序仍然可以不同。解析两者并比较它们的预期字段以建立值级别的等效性,而不是声明原始文本相等。
打开递归键排序,两个示例都以相同的对象顺序渲染,而 `steps` 保留 `cut`,然后是 `pack`。这对于人类差异很有用,但它仍然是 ToolAcre 的标准化,而不是 RFC 8785 证明。如果第二个数组是 `["pack","cut"]`,排序键将正确地使差异可见,因为更改数组会更改表示的序列。
这不包括什么
键排序并未为每个应用程序定义深度相等。 `JSON.parse` 接受重复的名称,它保留最后一个值,因此格式化可以删除一个源包含重复的证据。 JavaScript 值中的大整数可能已经丢失了精度。域也可以将选定的数组视为集合,但 ToolAcre 无法推断该规则,因此永远不会对数组重新排序。
格式化程序也不比较模式、应用默认值、标准化 Unicode 或决定下游系统是否可以接受两个数字表示形式。这些是单独的合同。使用格式化来减少表示噪音,使用专门构建的结构比较来实现值相等,并使用指定的规范化器来获取精确的字节。将这些工作混合在“标准化”一词下会导致人们对实际比较的内容产生错误的信心。
要点:顺序对模型来说无关紧要,但对字节来说很重要
对象成员顺序在 RFC 8259 数据模型中没有意义,而数组顺序则有意义。源字节仍然记录顺序和空格,因此散列、签名和文本差异会观察到面向值的比较可能会忽略的区别。在选择工具之前先说明哪一层重要:文本身份、解析值等价性和协议定义的规范身份是三个不同的问题。
ToolAcre 仅间接支持前两个工作流程:一致的格式可以澄清文本差异,递归字母对象键排序可以使人工比较更加安静。数组永远不会排序。结果不是 RFC 8785 规范 JSON,并且不应像它那样进行签名。当词汇证据很重要时,保留原始输入,特别是因为解析重复键仅保留最后一个值。