[ACCEPTED]-JSON.stringify() array bizarreness with Prototype.js-prototypejs

Accepted answer
Score: 82

Since JSON.stringify has been shipping with 5 some browsers lately, I would suggest using 4 it instead of Prototype’s toJSON. You would 3 then check for window.JSON && window.JSON.stringify 2 and only include the json.org library otherwise 1 (via document.createElement('script')…). To resolve the incompatibilities, use:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}
Score: 81

The function JSON.stringify() defined in 18 ECMAScript 5 and above (Page 201 - the JSON Object, pseudo-code Page 205), uses the function toJSON() when available 17 on objects.

Because Prototype.js (or another 16 library that you are using) defines an Array.prototype.toJSON() function, arrays 15 are first converted to strings using Array.prototype.toJSON() then 14 string quoted by JSON.stringify(), hence 13 the incorrect extra quotes around the arrays.

The 12 solution is therefore straight-forward and 11 trivial (this is a simplified version of 10 Raphael Schweikert's answer):

delete Array.prototype.toJSON

This produces 9 of course side effects on libraries that 8 rely on a toJSON() function property for 7 arrays. But I find this a minor inconvenience 6 considering the incompatibility with ECMAScript 5 5.

It must be noted that the JSON Object 4 defined in ECMAScript 5 is efficiently implemented 3 in modern browsers and therefore the best 2 solution is to conform to the standard and 1 modify existing libraries.

Score: 16

A possible solution which will not affect 5 other Prototype dependencies would be:

var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
    var _array_tojson = Array.prototype.toJSON;
    delete Array.prototype.toJSON;
    var r=_json_stringify(value);
    Array.prototype.toJSON = _array_tojson;
    return r;
};

This 4 takes care of the Array toJSON incompatibility 3 with JSON.stringify and also retains toJSON 2 functionality as other Prototype libraries 1 may depend on it.

Score: 9

Edit to make a bit more accurate:

The problem 13 key bit of code is in the JSON library from 12 JSON.org (and other implementations of ECMAScript 11 5's JSON object):

if (value && typeof value === 'object' &&
  typeof value.toJSON === 'function') {
  value = value.toJSON(key);
}

The problem is that the 10 Prototype library extends Array to include 9 a toJSON method, which the JSON object will 8 call in the code above. When the JSON object 7 hits the array value it calls toJSON on 6 the array which is defined in Prototype, and 5 that method returns a string version of 4 the array. Hence, the quotes around the 3 array brackets.

If you delete toJSON from 2 the Array object the JSON library should 1 work properly. Or, just use the JSON library.

Score: 4

I think a better solution would be to include 6 this just after prototype has been loaded

JSON = JSON || {};

JSON.stringify = function(value) { return value.toJSON(); };

JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };

This 5 makes the prototype function available as 4 the standard JSON.stringify() and JSON.parse(), but 3 keeps the native JSON.parse() if it is available, so 2 this makes things more compatible with older 1 browsers.

Score: 2

I'm not that fluent with Prototype, but 4 I saw this in its docs:

Object.toJSON({"a":[1,2]})

I'm not sure if this 3 would have the same problem the current 2 encoding has, though.

There's also a longer 1 tutorial about using JSON with Prototype.

Score: 2

This is the code I used for the same issue:

function stringify(object){
      var Prototype = window.Prototype
      if (Prototype && Prototype.Version < '1.7' &&
          Array.prototype.toJSON && Object.toJSON){
              return Object.toJSON(object)
      }
      return JSON.stringify(object)
}

You 4 check if Prototype exists, then you check 3 the version. If old version use Object.toJSON 2 (if is defined) in all other cases fallback 1 to JSON.stringify()

Score: 1

Here's how I'm dealing with it.

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);

0

Score: 1

My tolerant solution checks whether Array.prototype.toJSON 3 is harmful for JSON stringify and keeps 2 it when possible to let the surrounding 1 code work as expected:

var dummy = { data: [{hello: 'world'}] }, test = {};

if(Array.prototype.toJSON) {
    try {
        test = JSON.parse(JSON.stringify(dummy));
        if(!test || dummy.data !== test.data) {
            delete Array.prototype.toJSON;
        }
    } catch(e) {
        // there only hope
    }
}
Score: 1

As people have pointed out, this is due 8 to Prototype.js - specifically versions 7 prior to 1.7. I had a similar situation 6 but had to have code that operated whether 5 Prototype.js was there or not; this means 4 I can't just delete the Array.prototype.toJSON 3 as I'm not sure what relies on it. For that 2 situation this is the best solution I came 1 up with:

function safeToJSON(item){ 
    if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
        return JSON.stringify(item); //sane behavior
    } else { 
        return item.toJSON(); // Prototype.js nonsense
    }
}

Hopefully it will help someone.

Score: 0

If you don't want to kill everything, and 6 have a code that would be okay on most browsers, you 5 could do it this way :

(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
  if (true ||typeof (Prototype) !== 'undefined') {
    // First, ensure we can access the prototype of an object.
    // See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
    if(typeof (Object.getPrototypeOf) === 'undefined') {
      if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          return object.__proto__;
        };
      } else {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          // May break if the constructor has been changed or removed
          return object.constructor ? object.constructor.prototype : undefined;
        }
      }
    }

    var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
    JSON.stringify = function stringify (obj) {
      var obj_prototype = Object.getPrototypeOf(obj),
          old_json = obj_prototype.toJSON, // We save the toJSON of the object
          res = null;
      if (old_json) { // If toJSON exists on the object
        obj_prototype.toJSON = undefined;
      }
      res = _json_stringify.apply(this, arguments);
      if (old_json)
        obj_prototype.toJSON = old_json;
      return res;
    };
  }
}.call(this));

This seems complex, but 4 this is complex only to handle most use 3 cases. The main idea is overriding JSON.stringify to remove 2 toJSON from the object passed as an argument, then 1 call the old JSON.stringify, and finally restore it.

More Related questions