Aristotle writes on tragedy:
The protagonist should be renowned and prosperous, so his change of fortune can be from good to bad. This change “should come about as the result, not of vice, but of some great error or frailty in a character.” - Aristotle, Poetics
If this is so, then the tale of json.js is a tragic tale. But it is also a tale of redemption.
Setting the Stage
JavaScript Object Notation (JSON) is a text format for describing simple data structures composed of arrays, maps, strings, and numbers. I say "simple" because JSON has no way of representing duplicate objects or cyclic references. It can only encode "tree-shaped" objects.
In JSON's native JavaScript, a JSON in the code is an expression that evaluates to an object. If the JSON is inside a string then it can be turned into an object with the eval()
function. Consequently, JSON is the sea-monkey of web-development: pass a JSON string up to the client, Just Add Water (eval()
it) and you get an Instant Object. This is a very easy way to pass structured data from server to client.
It isn't perfect, though. First of all, eval()
doesn't just evaluate objects, it executes arbitrary code. This can hide bugs by quietly accepting invalid JSON strings — for example, eval("")
won't throw an exception, even though the empty string is not a valid JSON string.
The other, more serious problem is that JavaScript doesn't (or didn't; see below) provide a way to serialize an object to a JSON string. This makes it hard to pass JSON down to the server.
Renown and Prosperity
Both of these problems were solved by a small library called json.js.
By simply including this script on a page, you could use methods on the core JavaScript types to evaluate JSON strings:
var positionJson = ' {"top":40, "left":60 } ';
var positionObject = positionJson.parseJSON();
and serialize objects:
var contactObject = {name:"Oran Looney",
phoneNumbers: [ "123-456-7890", "321-654-0987" ]
};
var contactJson = contactObject.toJSONString();
json.js took advantage of the fact that JavaScript allows you to dynamically extend core types like Array, Object, and String by adding the polymorphic toJSONString()
method to the prototype of each core type. It's an elegant design, with a concise and easy-to-remember API.
And json.js did indeed achieve renown: for a long time it was the JavaScript implementation listed on json.org, the evangelical website devoted to spreading the gospel of JSON.
Error and Fraility
They say this fruit be like unto the world So sweet. Or like, say I, the heart of man So red without and yet within, unclue'd, We find the worm, the rot, the flaw. However glows his bloom the bite Proves many a man be rotten at the core. -Terry Pratchett
JavaScript exposes prototype objects for all core classes, and adding methods to a prototype is a good way to add functionality to them. With one exception: Object.prototype is verboten. The reason is simple: it breaks the JavaScript for...in
loop. You would expect
for (attribute in {}) alert(attribute);
to execute quietly, because the empty object {}
has no attributes. But with json.js, you see
In fact, toJSONString will show up in every Object you iterate over using for..in
, because json.js installed it in the global Object prototype. You can't use for...in
unless you handle this awkward special case (using hasOwnProperty()
), and you generally can't trust third-party libraries to do this.
Fall
It's possible to argue that modifying the Object prototype is legal and that it's the responsibility of the programmer to understand and correct for this, but it still isn't good style to make more work for other people. The fact is, it's cleaner to just leave Object.prototype alone.
It wasn't long before before developers were finding this out first hand, typically after a long debugging session. Third-party libraries began providing their own JSON parsers and serializers. The JavaScript page at json.org now offers an alternative library, json2.js, which does not touch the Object.prototype. No one should use json.js anymore, although it's still available on an orphan page at json.org. (Some of its problems and drama have been documented in the comments).
Redemption
Science is organized common sense where many a beautiful theory was killed by an ugly fact. - Thomas Huxley
Sometimes a good design run aground on implementation details. You might be encouraged to know that json.js
is getting a second chance: it's been adopted as the reference implementation for JSON parsing and serialization in JavaScript 2.0. The toJSONString()
method will be adopted into core JavaScript. (It will also be possible to mark object properties as non-enumerable, hiding them from for...in
and preventing similar future problems.) Once 2.0 reaches the mainstream, we'll be able to express ourselves just as the original json.js envisioned.
- Oran Looney November 27th 2007
Thanks for reading. This blog is in "archive" mode and comments and RSS feed are disabled. We appologize for the inconvenience.