Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is JSON.stringify() deterministic in V8?

I've not seen (yet?) JSON.stringify to be non-deterministic in Node.JS.

There is no guarantee it to be deterministic on the specification level.

But what about V8; Is its implementation there deterministic? Is there a guarantee for it to remain deterministic for future V8 versions?

Edit:

With deterministic I mean that following assertion is true no matter what the value of json_str is. (Given the value is a valid JSON string.)

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

Edit 2:

Actually, I'm also interested for the following assertion to be true

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

which is not the case (see answers).

like image 984
brillout Avatar asked Feb 27 '17 16:02

brillout


2 Answers

To clarify jmrk's answer;

According to the spec, integer keys are serialized in numeric order and non-integer keys in chronological order of property creation, e.g.;

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}');

Therefore following assertion is guaranteed to be true

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

but two "deep equal" objects may be serialized into different strings;

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}');

Spec quote;

  1. Let keys be a new empty List.
  2. For each own property key P of O that is an integer index, in ascending numeric index order, do

    a. Add P as the last element of keys.

  3. For each own property key P of O that is a String but is not an integer index, in ascending chronological order of property creation, do

    a. Add P as the last element of keys.

  4. For each own property key P of O that is a Symbol, in ascending chronological order of property creation, do

    a. Add P as the last element of keys.

  5. Return keys.

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

like image 126
brillout Avatar answered Sep 28 '22 16:09

brillout


If by "deterministic" you mean enumeration order of the object's properties: that is actually specified, and V8 follows the spec. See https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys. [Edit: this is the answer to your clarified definition, so yes, JSON.stringify is deterministic in that sense.]

If by "deterministic" you mean "always returns the same string for the same input object", then, well, no :-)

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

Proxy objects and the replacer argument to JSON.stringify can also be used to create arbitrary behavior (even though JSON.stringify itself always does the same thing).

If by "deterministic" you mean something else, please specify.

like image 23
jmrk Avatar answered Sep 28 '22 15:09

jmrk