/**
* Provides methods to parse JSON strings and convert objects to JSON strings.
* @module json
* @requires yahoo
* @class YAHOO.lang.JSON
* @static
*/
YAHOO.namespace('lang');
YAHOO.lang.JSON = {
/**
* Parse a JSON string, returning the native JavaScript representation.
* Only minor modifications from http://www.json.org/json.js.
* @param s {string} JSON string data
* @param filter {function} (optional) function(k,v) passed each key value pair of object literals, allowing pruning or altering values
* @return {MIXED} the native JavaScript representation of the JSON string
* @throws SyntaxError
* @method parse
* @static
* @public
*/
parse : function (s,filter) {
var j;
function walk(k, v) {
var i, n;
if (v && typeof v === 'object') {
for (i in v) {
if (YAHOO.lang.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(s.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('(' + s + ')');
// 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');
},
/**
* Converts an arbitrary value to a JSON string representation.
* Cyclical object or array references are replaced with null.
* If a whitelist is provided, only matching object keys will be included.
* If a depth limit is provided, objects and arrays at that depth will
* be stringified as empty.
* @param o {MIXED} any arbitrary object to convert to JSON string
* @param w {Array} (optional) whitelist of acceptable object keys to include
* @param d {number} (optional) depth limit to recurse objects/arrays (practical minimum 1)
* @return {string} JSON string representation of the input
* @method stringify
* @static
* @public
*/
stringify : function (o,w,d) {
var l = YAHOO.lang,
// Regex used to encode strings as safe JSON values
str_re = /["\\\x00-\x1f]/g,
// Character substitution map used by regex to prepare strings
m = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
// Processing stack used to prevent cyclical references
pstack = [];
/**
* Encode odd characters. Translated characters are cached.
* @private
*/
function _encodeChar(c) {
if (!m[c]) {
var a = c.charCodeAt();
m[c] = '\\u00' + Math.floor(a / 16).toString(16) +
(a % 16).toString(16);
}
return m[c];
}
/**
* zero pad single digits in dates.
* @private
*/
function _zeroPad(v) {
return v < 10 ? '0' + v : v;
}
/**
* Wrap string values and object keys in double quotes after replacing
* any odd characters.
* @private
*/
function _string(o) {
return '"' + o.replace(str_re, _encodeChar) + '"';
}
/**
* Worker function. Fork behavior on data type and recurse objects and
* arrays per the configured depth.
* @private
*/
function _stringify(o,w,d) {
var t = typeof o,
i,len,j, // array iteration
k,v, // object iteration
vt, // typeof v during iteration
a; // composition array for performance over string concat
// String
if (t === 'string') {
return _string(o);
}
// native boolean and Boolean instance
if (t === 'boolean' || o instanceof Boolean) {
return String(o);
}
// native number and Number instance
if (t === 'number' || o instanceof Number) {
return isFinite(o) ? String(o) : 'null';
}
// Date
if (o instanceof Date) {
return ['"', o.getUTCFullYear(), '-',
_zeroPad(o.getUTCMonth() + 1),'-',
_zeroPad(o.getUTCDate()), 'T',
_zeroPad(o.getUTCHours()), ':',
_zeroPad(o.getUTCMinutes()), ':',
_zeroPad(o.getUTCSeconds()), 'Z"'].join('');
}
// Array
if (l.isArray(o)) {
// Check for cyclical references
for (i = 0, len = pstack.length; i < len; ++i) {
if (pstack[i] === o) {
return 'null';
}
}
// Add the array to the processing stack
pstack[pstack.length] = o;
a = [];
// Only recurse if we're above depth config
if (d > 0) {
for (i = 0, len = o.length; i < len; ++i) {
a[i] = _stringify(o[i],w,d-1);
}
}
// remove the array from the stack
pstack.pop();
return '[' + a.join(',') + ']';
}
// Object
if (t === 'object' && o) {
// Check for cyclical references
for (i = 0, len = pstack.length; i < len; ++i) {
if (pstack[i] === o) {
return 'null';
}
}
// Add the object to the processing stack
pstack[pstack.length] = o;
a = [];
// Only recurse if we're above depth config
if (d > 0) {
// If whitelist provided, take only those keys
if (w) {
for (i = 0, j = 0, len = w.length; i < len; ++i) {
v = o[w[i]];
vt = typeof v;
// Omit invalid values
if (vt !== 'undefined' && vt !== 'function') {
a[j++] = _string(w[i]) + ':' + _stringify(v,w,d-1);
}
}
// Otherwise, take all valid object properties
// omitting the prototype chain properties
} else {
j = 0;
for (k in o) {
if (typeof k === 'string' && l.hasOwnProperty(o,k)) {
v = o[k];
vt = typeof v;
if (vt !== 'undefined' && vt !== 'function') {
a[j++] = _string(k) + ':' + _stringify(v,w,d-1);
}
}
}
}
}
// Remove the object from processing stack
pstack.pop();
return '{' + a.join(',') + '}';
}
return 'null';
}
// process the input
d = d >= 0 ? d : 1/0; // Default depth to POSITIVE_INFINITY
return _stringify(o,w,d);
}
};