Skip to content

Latest commit

 

History

History
144 lines (117 loc) · 4.65 KB

README.md

File metadata and controls

144 lines (117 loc) · 4.65 KB

JSON in JavaScript

Douglas Crockford
[email protected]

2010-11-18

JSON is a light-weight, language independent, data interchange format. See: http://www.JSON.org/

The files in this collection implement JSON encoders/decoders in JavaScript.

JSON became a built-in feature of JavaScript when the ECMAScript Programming Language Standard - Fifth Edition was adopted by the ECMA General Assembly in December 2009. Most of the files in this collection are for applications that are expected to run in obsolete web browsers. For most purposes, json2.js is the best choice.

json2.js

This file creates a JSON property in the global object, if there isn't already one, setting its value to an object containing a stringify method and a parse method. The parse method uses the eval method to do the parsing, guarding it with several regular expressions to defend against accidental code execution hazards. On current browsers, this file does nothing, prefering the built-in JSON object.

json.js

This file does everything that json2.js does. It also adds a toJSONString method and a parseJSON method to Object.prototype. Use of this file is not recommended.

json_parse.js

This file contains an alternative JSON parse function that uses recursive descent instead of eval.

json_parse_state.js

This files contains an alternative JSON parse function that uses a state machine instead of eval.

cycle.js

This file contains two functions, JSON.decycle and JSON.retrocycle, which make it possible to encode cyclical structures and dags in JSON, and to then recover them. JSONPath is used to represent the links. http://GOESSNER.net/articles/JsonPath/

Effectively this allows to create a deep copy of an object or to stringify it. Even with some cyclical objects.

How JSON.decycle works

Decycle handles duplicate references. So for example you can decycle an array containg a reference to itself:

var a = [];
a[0] = 'something'
a[1] = a;
console.log(JSON.stringify(
  JSON.decycle(a)
));
// -> `["something",{"$ref":"$"}]`

So what decycle did was replacing a with a special object ({"$ref":"$"}). That object is just a plain object, but it is something JSON.retrocycle would understand.

The duplicate references (which might be forming cycles) are replaced with an object of the form:

{$ref: PATH}

Where the PATH is a JSONPath string that locates the first occurance.

JSONPath is used to locate the unique object. $ indicates the top level of the object or array. [NUMBER] or [STRING] indicates a child member or property.

Deep clone

Lets create a simple helper class:

Cycles = class {
  stringify(obj) {
    return JSON.stringify(
      JSON.decycle(a)
    );
  }
  parse(str) {
    return JSON.retrocycle(
      JSON.parse(str)
    );
  }
  clone(obj) {
    return this.parse(
      this.stringify(obj)
    );
  }
}
cycles = new Cycles();

So now we can easily do cloning. For example:

var a = [];
a[0] = 'something'
a[1] = a;
console.log(cycles.stringify(a));
b = cycles.clone(a);
// at this point a and be will be identical
console.log(a, b);
// but b is not the same object
b.push('test');
console.log(a, b);

You will notice that b.lenght is 3, but a.length is still 2. Also note that b[1] will be the same array (b[1] = b).

Dumping DOM nodes

This fork contains extra parameter for JSON.decycle (JSON.decycle = function (object, stringifyNodes)). When stringifyNodes is true then DOM nodes are more identifiable. This is great for debugging.

Use JSON.stringify(JSON.decycle(variable, true)) if you expect that variable can contain DOM Nodes.

Here is how the two versions of decycle compare:

var a = [];
a[0] = 'something'
a[1] = document.getElementById('some-element');

console.log(JSON.stringify(
  JSON.decycle(a)
));
// -> ["something",{}]

console.log(JSON.stringify(
  JSON.decycle(a, true)
));
// -> ["something","div#some-element"]

So nodes are kind of replaced with their selectors. In a way. Here are some examples:

  • id: <div id="some-element"> -> "div#some-element"
  • classes: <div class="some-class hidden"> -> "div.some-class.hidden{textContent:Lorem ipsum dolor si...}" (text content is shortened to 20 chars)
  • other elements: <tab-component> -> "tab-component{textContent:Lorem ipsum dolor si...}"

Note! The stringifyNodes option is good for debugging, but retrocycle will not create DOM elements. For that you would have to create your own serializer and deserializer.