JSON中的JSON.stringify()是否具有确定性?

时间:2017-02-27 16:48:40

标签: javascript node.js v8 memoization

我还没有看到(但是?)JSON.stringify在Node.JS中是不确定的。

无法保证它在规范级别上具有确定性。

但是V8呢? 它的实施是否具有确定性? 是否可以保证它在未来的V8版本中保持确定性?

编辑:

确定性意味着无论json_str的值是什么,跟随断言都是正确的。 (鉴于该值是有效的JSON字符串。)

const obj = JSON.parse(json_str);
assert(JSON.stringify(obj)===JSON.stringify(obj)); // always true

编辑2:

实际上,我也对下面的断言感兴趣

if( deepEqual(obj1, obj2) ) {
    assert(JSON.stringify(obj1)===JSON.stringify(obj2))
}

情况并非如此(见答案)。

5 个答案:

答案 0 :(得分:9)

澄清jmrk的回答;

根据规范,整数键按数字顺序序列化,非整数键按属性创建的时间顺序排列,例如;

var o = {};
o[2] = 2;
o.a = 3;
o.b = 4;
o["1"] = 1;

assert(JSON.stringify(o)==='{"1":1,"2":2,"a":3,"b":4}');

因此,以下断言保证是真的

if( obj1 === obj2 ) {
  assert(JSON.stringify(obj1) === JSON.stringify(obj2));
}

但两个"深度相等"对象可以序列化为不同的字符串;

var obj1 = {};
obj1["a"] = true;
obj1["b"] = true;
assert(JSON.stringify(obj1)==='{"a":true,"b":true}');

var obj2 = {};
obj2["b"] = true;
obj2["a"] = true;
assert(JSON.stringify(obj2)==='{"b":true,"a":true}');

规格引用;

  
      
  1. 让密钥成为新的空列表。
  2.   
  3. 对于作为整数索引的O的每个自有属性键P,按升序数字索引顺序,执行

         

    一个。添加P作为键的最后一个元素。

  4.   
  5. 对于作为字符串但不是整数索引的O的每个自有属性键P,按属性创建的按时间顺序递增,执行

         

    一个。添加P作为键的最后一个元素。

  6.   
  7. 对于作为符号的O的每个自有属性键P,按照属性创建的递增时间顺序,执行

         

    一个。添加P作为键的最后一个元素。

  8.   
  9. 返回键。
  10.   

来自https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys

答案 1 :(得分:6)

如果通过"确定性"你的意思是对象属性的枚举顺序:实际指定了 ,而V8遵循规范。见https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys。 [编辑:这是你澄清定义的答案,所以是的,JSON.stringify在这个意义上是确定性的。]

如果通过"确定性"你的意思是"总是为同一个输入对象返回相同的字符串",然后,好吧,没有: - )

> var o = { toJSON: function() { return Math.random(); } }
> JSON.stringify(o);
< "0.37377773963616434"
> JSON.stringify(o);
< "0.8877065604993732"

Proxy个对象和replacer JSON.stringify参数也可以用来创建任意行为(即使JSON.stringify本身也是一样的。)

如果通过&#34;确定性&#34;你的意思是其他的,请注明。

答案 2 :(得分:3)

您的条款中的决定论归结为:

  1. 订单=&gt;对象数据是否会以相同的顺序编组?
  2. 是的,通过对象数据的遍历发生在相同的路线中。总是

    1. Content =&gt;对象数据是否会用相同的内容封送?
    2. 是的,除非通过toJSON引入的任意性覆盖为上面解释的@jmrk。

      1. Concurrency =&gt;是否会在检查之间修改对象数据?
      2. 不,V8脚本运行器是单线程的,因此不会发生混乱的访问。

        1. 规范=&gt;规范中是否存在违反决定论的条款?
        2. 不,除了上下文替换器/覆盖之外,解析器和stringify应该每次都生成相同的数据。

          1. 兼容性=&gt;所有stringify方法都会生成兼容数据吗?
          2. 不,规范在列出对象字段的顺序上并不清楚,因此实现可以自由地遍历对象,这意味着数据可能与“目的”相同。和&#39;精神&#39;,不可比较的字节到字节。

            希望这有帮助!

答案 3 :(得分:3)

如果有人想找一个能让JSON转储可预测的函数,我写了一个:

const sortObj = (obj) => (
  obj === null || typeof obj !== 'object'
  ? obj
  : Array.isArray(obj)
  ? obj.map(sortObj)
  : Object.assign({}, 
      ...Object.entries(obj)
        .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
        .map(([k, v]) => ({ [k]: sortObj(v) }),
    ))
);

这是一个组合的确定性JSON转储:

const deterministicStrigify = obj => JSON.stringify(deterministic(sortObj))

它适用于上面的例子:

> obj1 = {};
> obj1.b = 5;
> obj1.a = 15;

> obj2 = {};
> obj2.a = 15;
> obj2.b = 5;

> deterministicStrigify(obj1)
'{"a":15,"b":5}'
> deterministicStrigify(obj2)
'{"a":15,"b":5}'
> JSON.stringify(obj1)
'{"b":5,"a":15}'
> JSON.stringify(obj2)
'{"a":15,"b":5}'

答案 4 :(得分:0)

@fodma1 的方法可以更简单地使用 JSON.stringify 的第二个参数('replacer' 函数)来实现:

const deterministicReplacer = (_, v) =>
  typeof v !== 'object' || v === null || Array.isArray(v) ? v :
    Object.fromEntries(Object.entries(v).sort(([ka], [kb]) => 
      ka < kb ? -1 : ka > kb ? 1 : 0));

那么:

JSON.stringify({b: 1, a: 0, c: {e: [], d: null, f: 1}}, deterministicReplacer, 2);
JSON.stringify({c: {f: 1, e: [], d: null}, b: 1, a: 0}, deterministicReplacer, 2);

都给:

{"a":0,"b":1,"c":{"d":null,"e":[],"f":1}}

出于速度的原因,我也切换到朴素的字符串比较,假设我们只关心排序顺序的可重复性。