You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
10 KiB
319 lines
10 KiB
/*
|
|
json.js
|
|
2007-10-10
|
|
|
|
Public Domain
|
|
|
|
This file adds these methods to JavaScript:
|
|
|
|
array.toJSONString(whitelist)
|
|
boolean.toJSONString()
|
|
date.toJSONString()
|
|
number.toJSONString()
|
|
object.toJSONString(whitelist)
|
|
string.toJSONString()
|
|
These methods produce a JSON text from a JavaScript value.
|
|
It must not contain any cyclical references. Illegal values
|
|
will be excluded.
|
|
|
|
The default conversion for dates is to an ISO string. You can
|
|
add a toJSONString method to any date object to get a different
|
|
representation.
|
|
|
|
The object and array methods can take an optional whitelist
|
|
argument. A whitelist is an array of strings. If it is provided,
|
|
keys in objects not found in the whitelist are excluded.
|
|
|
|
string.parseJSON(filter)
|
|
This method parses a JSON text to produce an object or
|
|
array. It can throw a SyntaxError exception.
|
|
|
|
The optional filter parameter is a function which can filter and
|
|
transform the results. It receives each of the keys and values, and
|
|
its return value is used instead of the original value. If it
|
|
returns what it received, then structure is not modified. If it
|
|
returns undefined then the member is deleted.
|
|
|
|
Example:
|
|
|
|
// Parse the text. If a key contains the string 'date' then
|
|
// convert the value to a date.
|
|
|
|
myData = text.parseJSON(function (key, value) {
|
|
return key.indexOf('date') >= 0 ? new Date(value) : value;
|
|
});
|
|
|
|
It is expected that these methods will formally become part of the
|
|
JavaScript Programming Language in the Fourth Edition of the
|
|
ECMAScript standard in 2008.
|
|
|
|
This file will break programs with improper for..in loops. See
|
|
http://yuiblog.com/blog/2006/09/26/for-in-intrigue/
|
|
|
|
This is a reference implementation. You are free to copy, modify, or
|
|
redistribute.
|
|
|
|
Use your own copy. It is extremely unwise to load untrusted third party
|
|
code into your pages.
|
|
*/
|
|
|
|
/*jslint evil: true */
|
|
|
|
// Augment the basic prototypes if they have not already been augmented.
|
|
|
|
if (!Object.prototype.toJSONString) {
|
|
|
|
Array.prototype.toJSONString = function (w) {
|
|
var a = [], // The array holding the partial texts.
|
|
i, // Loop counter.
|
|
l = this.length,
|
|
v; // The value to be stringified.
|
|
|
|
// For each value in this array...
|
|
|
|
for (i = 0; i < l; i += 1) {
|
|
v = this[i];
|
|
switch (typeof v) {
|
|
case 'object':
|
|
|
|
// Serialize a JavaScript object value. Treat objects thats lack the
|
|
// toJSONString method as null. Due to a specification error in ECMAScript,
|
|
// typeof null is 'object', so watch out for that case.
|
|
|
|
if (v && typeof v.toJSONString === 'function') {
|
|
a.push(v.toJSONString(w));
|
|
} else {
|
|
a.push('null');
|
|
}
|
|
break;
|
|
|
|
case 'string':
|
|
case 'number':
|
|
case 'boolean':
|
|
a.push(v.toJSONString());
|
|
break;
|
|
default:
|
|
a.push('null');
|
|
}
|
|
}
|
|
|
|
// Join all of the member texts together and wrap them in brackets.
|
|
|
|
return '[' + a.join(',') + ']';
|
|
};
|
|
|
|
|
|
Boolean.prototype.toJSONString = function () {
|
|
return String(this);
|
|
};
|
|
|
|
|
|
Date.prototype.toJSONString = function () {
|
|
|
|
// Eventually, this method will be based on the date.toISOString method.
|
|
|
|
function f(n) {
|
|
|
|
// Format integers to have at least two digits.
|
|
|
|
return n < 10 ? '0' + n : n;
|
|
}
|
|
|
|
return '"' + this.getUTCFullYear() + '-' +
|
|
f(this.getUTCMonth() + 1) + '-' +
|
|
f(this.getUTCDate()) + 'T' +
|
|
f(this.getUTCHours()) + ':' +
|
|
f(this.getUTCMinutes()) + ':' +
|
|
f(this.getUTCSeconds()) + 'Z"';
|
|
};
|
|
|
|
|
|
Number.prototype.toJSONString = function () {
|
|
|
|
// JSON numbers must be finite. Encode non-finite numbers as null.
|
|
|
|
return isFinite(this) ? String(this) : 'null';
|
|
};
|
|
|
|
|
|
Object.prototype.toJSONString = function (w) {
|
|
var a = [], // The array holding the partial texts.
|
|
k, // The current key.
|
|
i, // The loop counter.
|
|
v; // The current value.
|
|
|
|
// If a whitelist (array of keys) is provided, use it assemble the components
|
|
// of the object.
|
|
|
|
if (w) {
|
|
for (i = 0; i < w.length; i += 1) {
|
|
k = w[i];
|
|
if (typeof k === 'string') {
|
|
v = this[k];
|
|
switch (typeof v) {
|
|
case 'object':
|
|
|
|
// Serialize a JavaScript object value. Ignore objects that lack the
|
|
// toJSONString method. Due to a specification error in ECMAScript,
|
|
// typeof null is 'object', so watch out for that case.
|
|
|
|
if (v) {
|
|
if (typeof v.toJSONString === 'function') {
|
|
a.push(k.toJSONString() + ':' +
|
|
v.toJSONString(w));
|
|
}
|
|
} else {
|
|
a.push(k.toJSONString() + ':null');
|
|
}
|
|
break;
|
|
|
|
case 'string':
|
|
case 'number':
|
|
case 'boolean':
|
|
a.push(k.toJSONString() + ':' + v.toJSONString());
|
|
|
|
// Values without a JSON representation are ignored.
|
|
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
// Iterate through all of the keys in the object, ignoring the proto chain
|
|
// and keys that are not strings.
|
|
|
|
for (k in this) {
|
|
if (typeof k === 'string' &&
|
|
Object.prototype.hasOwnProperty.apply(this, [k])) {
|
|
v = this[k];
|
|
switch (typeof v) {
|
|
case 'object':
|
|
|
|
// Serialize a JavaScript object value. Ignore objects that lack the
|
|
// toJSONString method. Due to a specification error in ECMAScript,
|
|
// typeof null is 'object', so watch out for that case.
|
|
|
|
if (v) {
|
|
if (typeof v.toJSONString === 'function') {
|
|
a.push(k.toJSONString() + ':' +
|
|
v.toJSONString());
|
|
}
|
|
} else {
|
|
a.push(k.toJSONString() + ':null');
|
|
}
|
|
break;
|
|
|
|
case 'string':
|
|
case 'number':
|
|
case 'boolean':
|
|
a.push(k.toJSONString() + ':' + v.toJSONString());
|
|
|
|
// Values without a JSON representation are ignored.
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Join all of the member texts together and wrap them in braces.
|
|
|
|
return '{' + a.join(',') + '}';
|
|
};
|
|
|
|
|
|
(function (s) {
|
|
|
|
// Augment String.prototype. We do this in an immediate anonymous function to
|
|
// avoid defining global variables.
|
|
|
|
// m is a table of character substitutions.
|
|
|
|
var m = {
|
|
'\b': '\\b',
|
|
'\t': '\\t',
|
|
'\n': '\\n',
|
|
'\f': '\\f',
|
|
'\r': '\\r',
|
|
'"' : '\\"',
|
|
'\\': '\\\\'
|
|
};
|
|
|
|
|
|
s.parseJSON = function (filter) {
|
|
var j;
|
|
|
|
function walk(k, v) {
|
|
var i, n;
|
|
if (v && typeof v === 'object') {
|
|
for (i in v) {
|
|
if (Object.prototype.hasOwnProperty.apply(v, [i])) {
|
|
n = walk(i, v[i]);
|
|
if (n !== undefined) {
|
|
v[i] = n;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return filter(k, v);
|
|
}
|
|
|
|
|
|
// Parsing happens in three stages. In the first stage, we run the text against
|
|
// a regular expression which looks for non-JSON characters. We are especially
|
|
// concerned with '()' and 'new' because they can cause invocation, and '='
|
|
// because it can cause mutation. But just to be safe, we will reject all
|
|
// unexpected characters.
|
|
|
|
// We split the first stage into 4 regexp operations in order to work around
|
|
// crippling deficiencies in IE's and Safari's regexp engines. First we replace
|
|
// all backslash pairs with '@' (a non-JSON character). Second, we replace all
|
|
// simple value tokens with ']' characters. Third, we delete all open brackets
|
|
// that follow a colon or comma or that begin the text. Finally, we look to see
|
|
// that the remaining characters are only whitespace or ']' or ',' or ':' or '{'
|
|
// or '}'. If that is so, then the text is safe for eval.
|
|
|
|
if (/^[\],:{}\s]*$/.test(this.replace(/\\./g, '@').
|
|
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
|
|
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
|
|
|
// In the second stage we use the eval function to compile the text into a
|
|
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
|
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
|
// in parens to eliminate the ambiguity.
|
|
|
|
j = eval('(' + this + ')');
|
|
|
|
// In the optional third stage, we recursively walk the new structure, passing
|
|
// each name/value pair to a filter function for possible transformation.
|
|
|
|
return typeof filter === 'function' ? walk('', j) : j;
|
|
}
|
|
|
|
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
|
|
|
throw new SyntaxError('parseJSON');
|
|
};
|
|
|
|
|
|
s.toJSONString = function () {
|
|
|
|
// If the string contains no control characters, no quote characters, and no
|
|
// backslash characters, then we can simply slap some quotes around it.
|
|
// Otherwise we must also replace the offending characters with safe
|
|
// sequences.
|
|
|
|
if (/["\\\x00-\x1f]/.test(this)) {
|
|
return '"' + this.replace(/[\x00-\x1f\\"]/g, function (a) {
|
|
var c = m[a];
|
|
if (c) {
|
|
return c;
|
|
}
|
|
c = a.charCodeAt();
|
|
return '\\u00' + Math.floor(c / 16).toString(16) +
|
|
(c % 16).toString(16);
|
|
}) + '"';
|
|
}
|
|
return '"' + this + '"';
|
|
};
|
|
})(String.prototype);
|
|
}
|