JavaScript Object Notation, or JSON, is a text format used to represent JavaScript objects as strings. Like other text formats, JSON is not dependent on a specific programming language. Because of this, JSON libraries have been created for a number of popular programming languages including C/C++, Java, and PHP. Originally, even JavaScript required libraries in order to use JSON. In an effort to increase speed and security, modern browsers have incorporated native JSON support. By processing JSON natively, performance overheads associated with JavaScript libraries are avoided. Security is also improved by removing calls to eval()
or untrusted libraries.
Browsers with native JSON support define a global object named JSON
. The JSON
object contains two methods, parse()
and stringify()
, which are used to serialize and deserialize objects.
Constructing Objects from JSON Strings
To build a JavaScript object from a JSON formatted string, the parse()
method is used. parse()
is used as a safer alternative to eval()
, because, while eval()
will execute arbitrary JavaScript code, parse()
was designed to only handle valid JSON strings.
The syntax of the parse()
method is shown below. The first argument, text
, is a JSON formatted string. If text
is not a valid JSON string, a SyntaxError
exception will be thrown. If no problems are encountered, parse()
returns a JavaScript object corresponding to the JSON string. parse()
also takes an optional second argument named reviver
which I’ll cover shortly.
JSON.parse(text[, reviver])
In the following example, the parse()
method is used to construct an object from a JSON string. The object, obj
, has two properties, foo
and bar
which hold the values 10
and 20
.
var string = "{\"foo\":10, \"bar\":20}";
var obj = JSON.parse(string);
// obj.foo is equal to 10
// obj.bar is equal to 20
The second argument to parse(), “reviver”, is a function that allows an object to be transformed during parsing. As each property is parsed from the JSON string it is run through the reviver function. The value returned by the reviver function is used as the property’s value in the constructed object. If an undefined value is returned by the reviver function then the property is removed from the object.
The reviver function itself takes two arguments, the property name (key) and its parsed value. The reviver should always check the key argument for the empty string. The reason is, after the reviver is called on each individual property, it is then called on the constructed object itself. On the last call to the reviver function, the empty string is passed as the key and the constructed object is passed as the value. An example reviver function would look like this:
function (key, value) {
// check for the top level object
if (key === "") {
// be sure to return the top level object
// otherwise the constructed object will be undefined
return value;
} else {
// return the original untransformed value
return value;
}
}
In the next example, another object is constructed from the same JSON string. However, in this example a custom reviver function named square() is used. As the name implies, the square() function squares the value of each property encountered during parsing. Due to the presence of the reviver function, the values of “foo” and “bar” become 100 and 400 respectively.
var square = function(key, value) { if (key === "") return value; else return value * value; }; var string = "{\"foo\":10, \"bar\":20}"; var obj = JSON.parse(string, square); // obj.foo is 100 // obj.bar is 400
Things to Remember
- Always use native JSON handling when possible.
- The JSON.parse() method builds a JavaScript object from a JSON string.
- A reviver function can transform an object during parsing.
- Always check a reviver function’s key argument for the empty string.
Calling the JSON.stringify()
method is the recommended way to perform serialization. The syntax for stringify()
is shown below. The first argument, value
, is the JavaScript object being stringified. stringify()
supports two other optional arguments named “replacer” and “space”.
JSON.stringify(value[, replacer[, space]])
Customized Serialization
It is possible to customize the stringification process. During serialization, objects are checked for a method named toJSON()
. If this method exists then it is called by stringify()
. Instead of processing the original object, stringify()
will serialize whatever value is returned by toJSON()
. This is how JavaScript’s Date
objects are serialized. Since JSON does not support the Date
type, Date
objects come equipped with a toJSON()
method.
The following example shows toJSON()
in action. In the example, an object named obj
is created with the fields “foo”, “bar”, and “baz”. When obj
is stringified, its toJSON()
method is called. In this example, toJSON()
returns a copy of obj
minus the “foo” field. The copy of obj
is serialized, resulting in a JSON string containing only the “bar” and “baz” fields.
var obj = {"foo":0, "bar":1, "baz":2};
obj.toJSON = function() {
var copy = {};
for (var key in this) {
if (key === "foo")
continue;
else
copy[key] = this[key];
}
return copy;
};
var json = JSON.stringify(obj);
//json is {"bar":1,"baz":2}
The replacer Argument
The stringify() method takes a second argument named “replacer”, a function similar to the “reviver” argument of JSON.parse()
. The replacer function takes two arguments, a key/value pair. First, the function is called with an empty key, and the object being serialized as the value. The replacer function should be sure to check for the empty key in order to handle this case. Next, each of the object’s properties and corresponding value are passed to the replacer, one-by-one. An example replacer function would look like this:
function(key, value) {
// check for the top level object
if (key === "")
return value;
else
return value;
}
It is important to handle the top level object properly. Typically, it is best to simply return the object’s value. In the following example, the top level object returns the string “foo”. Therefore, no matter how the object’s properties are handled, stringify()
will always return the string “foo”.
function(key, value) {
if (key === "")
return "foo";
else // this is now irrelevant
return value;
}
In the following example, an object is serialized using a custom replacer function named filter()
. The job of the filter()
function is to serialize numeric values only. Using the isNaN()
function ensures that only numeric fields are returned. All non-numeric fields return an undefined value. Fields that return undefined values are automatically removed from the stringified object. In this example, the replacer function causes the baz
field to be dropped because it holds a string.
function filter(key, value) {
var number = parseInt(value);
// check for the top level object
if (key === "")
return value;
else if (!isNaN(number))
return number;
}
var obj = {"foo":0, "bar":1, "baz":"x"};
var json = JSON.stringify(obj, filter);
// json is {"foo":0,"bar":1}
The Array Form of replacer
The replacer argument can also hold an array of strings. Each string represents the name of a field that should be serialized. Any field that is not included in the replacer array will not be included in the JSON string. In the following example, an object is defined with fields named foo
and bar
. An array is also defined, containing the strings foo
and baz
. During stringification, the bar
field is dropped because it is not part of the replacer array. Note that a baz
field is not created because, although it is defined in the replacer array, it is not defined in the original object. This leaves foo
as the only field in the stringified object.
var obj = {"foo":0, "bar":1};
var arr = ["foo", "baz"];
var json = JSON.stringify(obj, arr);
// json is {"foo":0}
Pretty-Printing
JSON strings are commonly viewed for logging and debugging purposes. In order to aid readability, the stringify()
function supports a third argument named “space”, which allows the developer to format whitespace in the resulting JSON string. This argument can be either a number of a string. If “space” is a number then up to ten space characters can be used as whitespace. If the value is less than one then no spaces are used. If the value exceeds ten then the maximum value of ten is used. If “space” is a string then that string is used as whitespace. If the string length is greater than ten then only the first ten characters are used. If “space” is omitted or null, then no whitespace is used. The following examples show the effect of the “space” argument on the stringification process. All of the examples assume the following JavaScript object:
var obj = {"foo":0, "bar":1, "baz":2};
The following calls to stringify()
all return the same JSON string. These calls cover the cases where “space” is omitted, null, undefined, a number less than one, or the empty string.
JSON.stringify(obj)
JSON.stringify(obj, null)
JSON.stringify(obj, null, null)
JSON.stringify(obj, null, undefined)
JSON.stringify(obj, null, 0)
JSON.stringify(obj, null, -1)
JSON.stringify(obj, null, "")
The resulting JSON string looks like this:
{"foo":0,"bar":1,"baz":2}
The next examples use the “space” argument to add whitespace to the JSON string.
JSON.stringify(obj, null, " ");
JSON.stringify(obj, null, 2);
The formatted JSON string is shown below. Notice that the string now spans multiple lines and the object’s properties are indented. For non-trivial objects, this format is much more readable.
{
"foo":0,
"bar":1,
"baz":2
}
The next example shows how nested objects are affected by “space”. Notice that baz
now stores an object instead of a scalar value. The “space” argument also uses two underscore characters instead of spaces.
var obj = {"foo":0, "bar":1, "baz":{"a":2}};
JSON.stringify(obj, null, "__");
The resulting JSON string is shown below. Underscores are now used as whitespace. The nested object stored in baz
is also indented further.
{
__"foo":0,
__"bar":1,
__"baz":{
____"a":2
__}
}
Things to Remember
- The
JSON.stringify()
method creates a JSON formatted string from a JavaScript object. - Stringification can be customized via a replacer function or
toJSON()
method. - The replacer argument can be either a function or an array.
- Objects can be pretty-printed by using the space argument.