On overriding toJSON

Anton Kovalyov
2 min readSep 9, 2014

This post was originally published on December 12th, 2010 on my personal blog.

Last week, we quietly rolled out an update that swapped our intramural library for cross-domain communications with easyXDM. We decided in favor of easyXDM simply because it is very well tested and supports more browsers (Firefox 1 anyone?) than the one we built.

The integration process was not that straightforward because embedded Disqus runs in a myriad of different environments; and you will be surprised by how much built-in stuff people tend to override or define carelessly. And Object.toJSON method is just one example.

ECMA-262 defines toJSON as a method that, if exists and callable, returns a value that JSON.stringify can use to convert an object into a JSON text. It is useful, for example, when converting a Date object.

That brings us to Prototype, a library that uses toJSON to convert objects into JSON. There is nothing wrong with this approach except that you can’t use built-in JSON functions (or even Crockford’s JSON2.js), if Prototype is around. Stringifier calls toJSON, gets back a JSON text and converts it into JSON again, adding extra quotes in the result. And we, developers of the embedded widgets, get an environment where JSON.parse(JSON.stringify([1,2,3])); returns a string instead of an array.

To get around this, I used a trick learned from reading easyXDM source:

var obj = {
a: [1, 2, 3]
}, json = "{\"a\":[1,2,3]}", JSON;

// First, check if JSON object is present and works correctly.

if (JSON && typeof JSON.stringify === "function" &&
JSON.stringify(obj).replace((/\s/g), "") === json) {
DISQUS.json = JSON;
}

// If there is a Prototype on the page, check if we can use
// Object.toJSON and String.evalJSON to stringify and parse
// objects respectively.

if (Object.toJSON) {
if (Object.toJSON(obj).replace((/\s/g), "") === json) {
DISQUS.json.stringify = Object.toJSON;
}
}

if (typeof String.prototype.evalJSON === "function") {
obj = json.evalJSON();
if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
// This is a working parse method.
DISQUS.json.parse = function(str){
return str.evalJSON();
};
}
}

// If we cannot use both JSON and Prototype,
// use our fork of JSON2.js.

if (!DISQUS.json.stringify || !DISQUS.json.parse) {
// Use JSON2.js
}

The code works fine until it hits the page where developers thought it was a good idea to make Array.toJSON act as a shortcut to JSON.stringify, or to corrupt toJSON method in some other way. This matters because JSON2.js follows the specification and tries to use toJSON method whenever possible.

For that kind of situations, we have to add additional check to detect when toJSON is corrupted:

var arr = [1,2,3], corrupted;

if (typeof arr.toJSON === "function") {
arr = arr.toJSON();
corrupted = !(arr && arr.length == 3 && arr[2] == 3);
}

This code tests only arrays but it should be pretty straightforward to test generic objects as well. And if you have any similar stories or interesting tricks, please share.

--

--