当键名具有数值时,JSON.parse()是否真的对属性进行排序?

Does JSON.parse() really sort properties when the key names have numeric values?

本文关键字:真的 是否 属性 排序 parse JSON      更新时间:2023-09-26

这里有很多关于这个问题的帖子,它们都包含很多断言,可以总结如下:

  1. 对象属性不保证以任何方式排序。
  2. JSON.parse()从不以任何方式排序属性。

显然,我们倾向于对上面的第一点毫无疑问,因此我们可以合理地期望,对于任何操作,属性都只是按照它们出现的顺序处理的。

[编辑,跟随@Bergi的评论:或者至少他们应该以随机顺序出现]

由此我们可以特别推断出#2应该是正确的。

但是看看这个片段:
(BTW注意:为了显示结果,下面的代码片段不使用console.log(),这可能会改变输出的顺序。相反,对象由for (key in obj)迭代,并在文档中显示输出)

var inputs = [
  '{"c": "C", "a": "A", "b": "B"}',
  '{"3": "C", "1": "A", "2": "B"}',
  '{"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}'
];
for (var i in inputs) {
  var json = inputs[i],
      parsed = JSON.parse(json),
      output = [];
  for (var j in parsed) {
    output.push(j + ': ' + parsed[j]);
  }
  document.write(`JSON: ${json}<br />Parsed: ${output.join(', ')})<hr />`);
}

它显示,给定一个具有无序键的JSON字符串:

  • 当输入具有非数值的键时,解析对象的属性的顺序与输入中的相同。这与上面的第二个假设是一致的。
  • 相反,当输入具有具有数值的键时(尽管它们是字符串,因此不会引发解析错误),解析对象将其属性排序为。这现在与#2假设相矛盾。
  • 更多:当有数字和非数字混合键值时,首先出现排序的数字属性,然后出现按原顺序排列的非数字属性

我第一次试图得出结论,实际上会有一个(未记录的?)功能,所以JSON.parse()遵循上面暴露的"规则"工作。

但是我有进一步研究的想法,所以下面的代码片段现在显示了仅仅编码对象的属性是如何排序的:

var objects = [
  [
    '{"c": "C", "a": "A", "b": "B"}',
    {"c": "C", "a": "A", "b": "B"}
  ],
  [
    '{"3": "C", "1": "A", "2": "B"}',
    {"3": "C", "1": "A", "2": "B"}
  ],
  [
    '{"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}',
    {"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}
  ]
];
for (var i in objects) {
  var object = objects[i],
      output = [];
  for (var j in object[1]) {
    output.push(j + ': ' + object[1][j]);
  }
  document.write(`Code: ${object[0]}<br />Object: ${output.join(', ')}<hr />`);
}

它会产生类似的观察结果,即无论它们被编码的顺序是什么,属性都按照上面的第三条规则存储:

  • 数字命名的属性都放在前面,排序
  • 接下来设置其他属性,按编码
  • 排序

所以这意味着JSON.parse()没有参与:事实上,它似乎是对象构建的一个基本过程。
同样,这似乎没有记录,至少就我所能找到的而言。

有什么真正权威的规则吗?


[编辑,感谢@Oriol的回答]实际上,综合来看:

  • 此行为符合ECMA规范规则。
  • 此规则应适用于保证特定订单的所有方法,但对于其他情况是可选的。
  • 然而,似乎现代浏览器都选择应用规则,无论方法涉及,因此明显的矛盾。

对象的属性没有顺序,所以JSON.parse不能对它们排序。但是,当您列出或枚举对象的属性时,顺序可能是定义良好的,也可能不是。

对于for...in循环和Object.keys循环都不一定

在ES6中为对象属性引入了定义良好的枚举顺序吗?,规范说

未指定枚举属性的机制和顺序

但是对于OrdinaryOwnPropertyKeys

对象有一个内部的[[OwnPropertyKeys]]方法,例如Object.getOwnPropertyNamesObject.getOwnPropertySymbols使用这个方法。

对于普通对象,该方法使用OrdinaryGetOwnProperty抽象操作,该操作以定义良好的顺序返回属性:

当抽象操作OrdinaryOwnPropertyKeys被调用时对象O,则采取以下步骤:

  1. keys为新的空List。
  2. 对于O的每个属性键P是一个整数索引,在升序数字索引顺序
    1. 添加P作为的最后一个元素。
  3. 对于O的每个属性键P,它是字符串但不是一个整数索引,按创建属性的升序排列
    1. 添加P作为的最后一个元素。
  4. 对于O的每个属性键P,它是一个符号,按属性创建的升序排列
    1. 添加P作为的最后一个元素。
  5. 键返回。

因此,由于OrdinaryOwnPropertyKeys需要一个顺序,实现可能决定在内部以该顺序存储属性,并在枚举时也使用它。这是你观察到的,但你不能依赖它。

也要注意非普通对象(例如代理对象)可能有另一个[[OwnPropertyKeys]]内部方法,所以即使使用Object.getOwnPropertyNames顺序仍然可能不同。

,因此我们可以合理地预期,对于任何操作,属性仅仅按照它们出现的顺序进行处理

这就是推理的缺陷所在。如果不能保证对象属性是有序的,我们必须假设任何操作都以它认为合适的顺序处理属性。

事实上,引擎在某种程度上对整数属性进行了特殊处理——它们就像数组索引,并且以比查找表更有效的格式存储。