Proposal: add an option to omit prototype of objects created by JSON.parse()

# 段垚 (8 years ago)

It is usually a bad practice to let a map-like object (an plain object used as a key-value map) have a prototype.

Objects created by JSON.parse() have a prototype by default, and we can get rid of them by:

JSON.parse(str, function(k, v) {

  if (v && typeof v === 'object' && !Array.isArray(v)) {

     v.__proto__ = null;

  }

  return v;

});

However, implementors warn that mutating prototype causes "performance hazards" [1].

How about adding an option to omit prototype of objects created by JSON.parse()?

E.g.:

JSON.parse(str, { noPrototype: true });

[1] developer.mozilla.org/en-US/docs/Web/JavaScript/The_performance_hazards_of__[[Prototype]]_mutation

# Danielle McLean (8 years ago)

From: 段垚 (mailto:duanyao at ustc.edu) Date: 28 September 2016 at 16:36:52

[I]mplementors warn that mutating prototype causes "performance hazards".

You don't actually need to mutate any prototypes to get prototypeless objects out of JSON.parse - the reviver function is allowed to return a new object instead, so you can create a fresh one with the correct prototype. For example:

JSON.parse(string, function(k, v) {
  if (v && typeof v === 'object' && !Array.isArray(v)) {
    return Object.assign(Object.create(null), v);
  }
  return v;
});

How about adding an option to omit prototype of objects created by JSON.parse()?

JSON.parse(str, { noPrototype: true });

While you can achieve the appropriate result already without extra language support, I think this particular situation is common enough that such an option might be a good idea.

How would you expect this new parameter to interact with the existing second parameter to JSON.parse, the reviver function? I'd suggest that the cleanest way to add the options would be for the second parameter to be either an object or function, like this:

JSON.parse(str, someFunc);
// equivalent to
JSON.parse(str, {reviver: someFunc});

Another approach would be to use two separate optional parameters, i.e., JSON.parse(text[, reviver][, options]), but generally that style is very messy.

# 段垚 (8 years ago)

在 2016/9/28 22:59, Danielle McLean 写道:

From: 段垚 (mailto:duanyao at ustc.edu) Date: 28 September 2016 at 16:36:52

[I]mplementors warn that mutating prototype causes "performance hazards". You don't actually need to mutate any prototypes to get prototypeless objects out of JSON.parse - the reviver function is allowed to return a new object instead, so you can create a fresh one with the correct prototype. For example:

JSON.parse(string, function(k, v) {
   if (v && typeof v === 'object' && !Array.isArray(v)) {
     return Object.assign(Object.create(null), v);
   }
   return v;
});

Yes, but creating a copy also has performance penalty. This proposal is made for performance.

How about adding an option to omit prototype of objects created by JSON.parse()?

JSON.parse(str, { noPrototype: true }); While you can achieve the appropriate result already without extra language support, I think this particular situation is common enough that such an option might be a good idea.

How would you expect this new parameter to interact with the existing second parameter to JSON.parse, the reviver function? I'd suggest that the cleanest way to add the options would be for the second parameter to be either an object or function, like this:

JSON.parse(str, someFunc);
// equivalent to
JSON.parse(str, {reviver: someFunc});

Looks reasonable.

Maybe we can simply add another method, say JSON.parseWithoutPrototype(). This makes feature detection easier.

# Michał Wadas (8 years ago)

Have you ever encountered performance issue because of copying object on deserialization?

gist.github.com/Ginden/381448a17f50c7669b9a3693742e3a3d For me results are:

Simple JSON.parse, 100000 iterations: 39.594999999997526ms JSON.parse with copy, 100000 iterations: 184.5699999999997ms JSON.parse with proto change, 100000 iterations: 89.75500000000102ms

# 段垚 (8 years ago)

在 2016/9/29 0:04, Michał Wadas 写道:

Have you ever encountered performance issue because of copying object on deserialization?

Not yet.

gist.github.com/Ginden/381448a17f50c7669b9a3693742e3a3d For me results are:

Simple JSON.parse, 100000 iterations: 39.594999999997526ms JSON.parse with copy, 100000 iterations: 184.5699999999997ms JSON.parse with proto change, 100000 iterations: 89.75500000000102ms

Thanks for writing this test. I got similar result on Firefox; while on Chrome and Edge, there is not much difference between coping and changing proto .

However, it is not easy to messure overhead of GC after copy and the effect of proto change on property access performance.

# Olivier Lalonde (8 years ago)

Given that JSON.parse doesn't necessarily return an object, would the noPrototype option would be ignored on e.g. JSON.parse('"some string"') or JSON.parse('true')?

# Danielle McLean (8 years ago)

From: Olivier Lalonde (mailto:olalonde at gmail.com) Date: 30 September 2016 at 07:21:10

Given that JSON.parse doesn't necessarily return an object, would the noPrototype option would be ignored on e.g. JSON.parse('"some string"') or JSON.parse('true')?

The noPrototype option should set the behaviour whenever the parser encounters a JSON object (i.e., key-value pairs wrapped in curly braces). If the JSON text to be parsed does not contain any objects, then the option will have no effect. 'true', 'some string', and '[1,2,3,"four"]' are examples of JSON texts which would not be affected by the noPrototype option. (Note that although an array is a JavaScript object, it's not a JSON object.)

Conversely, if the JSON text to be parsed contains multiple objects - for instance, JSON.parse('[{"first": 1}, {"second": 2}, {"third": 3}]', {noPrototype: true}) - then all of those objects should be constructed with a null prototype.

# Rick Waldron (8 years ago)

var o = JSON.parse('{}'); Object.setPrototypeOf(o, null);

# Tab Atkins Jr. (8 years ago)

On Thu, Oct 6, 2016 at 12:50 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

var o = JSON.parse('{}'); Object.setPrototypeOf(o, null);

That's not remotely correct, as it does nothing for anything other than the top object. (And it breaks things if the top-level value isn't an object.)