Introduction to JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format (if you are new to JSON, you can read more about it on the JSON website). It is notably used by APIs all over the web and is a fast alternative to XML in Ajax requests. Prototype 1.5.1 finally features JSON encoding and parsing support.

Prototype’s JSON implementation is largely based on the work of Douglas Crockford which will most likely be natively included in future versions of the main browsers. Crockford’s implementation is unfortunately unsuitable for use with Prototype because of the way it extends Object.prototype. (Note that this will no longer be an issue once it is natively implemented.)

Encoding

Prototype’s JSON encoding slightly differs from Crockford’s implementation as it does not extend Object.prototype. The following methods are available: Number#toJSON, String#toJSON, Array#toJSON, Hash#toJSON, Date#toJSON, and Object.toJSON.

If you are unsure of what type the data you need to encode is, your best bet is to use Object.toJSON like so:

var data = {name: 'Violet', occupation: 'character', age: 25 };
Object.toJSON(data);
//-> '{"name": "Violet", "occupation": "character", "age": 25}'

In other cases (i.e. if you know that your data is not an instance of Object), you can invoke the toJSON method instead:

$H({name: 'Violet', occupation: 'character', age: 25 }).toJSON();
//-> '{"name": "Violet", "occupation": "character", "age": 25}'

Furthermore, if you are using custom objects, you can set your own toJSON method which will be used by Object.toJSON. For example:

var Person = Class.create({
  initialize: function(name, age) {
    this.name = name;
    this.age = age;
  },  
  toJSON: function() {
    return ('My name is ' + this.name + 
      ' and I am ' + this.age + ' years old.').toJSON();
  }
});
var john = new Person('John', 49);
Object.toJSON(john);
//-> '"My name is John and I am 49 years old."'

Finally, using Element.addMethods you can create custom toJSON methods targeted at specific elements.

<input id="firstname" name="firstname" value="john" />
Element.addMethods('input', {
  toJSON: function(element) {
    element = $(element);
    return element.name.toJSON() + ": " + element.getValue().toJSON();
  }
})
    
Object.toJSON($('firstname'));
//-> '"firstname": "john"'

Parsing JSON

In JavaScript, parsing JSON is typically done by evaluating the content of a JSON string. Prototype introduces String#evalJSON to deal with this:

var data = '{ "name": "Violet", "occupation": "character" }'.evalJSON();
data.name;
//-> "Violet"

String#evalJSON takes an optional sanitize parameter, which, if set to true, checks the string for possible malicious attempts and prevents the evaluation and throws a SyntaxError if one is detected.

var data = 'grabUserPassword()'.evalJSON(true)
//-> SyntaxError: Badly formated JSON string: 'grabUserPassword()'

You should always set the sanitize parameter to true and an appropriate content-type header (application/json) for data coming from untrusted sources (external or user-created content) to prevent XSS attacks.

String#evalJSON internally calls String#unfilterJSON and automatically removes optional security comment delimiters (defined in Prototype.JSONFilter).

person = '/*-secure-\n{"name": "Violet", "occupation": "character"}\n*/'.evalJSON()
person.name;
//-> "Violet"

You should always set security comment delimiters (/*-secure-\n...*/) around sensitive JSON or JavaScript data to prevent Hijacking. (See this PDF document for more details.)

Using JSON with Ajax

Using JSON with Ajax is very straightforward, simply invoke String#evalJSON on the transport’s responseText property:

new Ajax.Request('/some_url', {
  method:'get',
  onSuccess: function(transport){
     var json = transport.responseText.evalJSON();
   }
});

If your data comes from an untrusted source, be sure to sanitize it:

new Ajax.Request('/some_url', {
  method:'get',
  requestHeaders: {Accept: 'application/json'},
  onSuccess: function(transport){
    var json = transport.responseText.evalJSON(true);
  }
});