2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * Provides methods to parse JSON strings and convert objects to JSON strings.
13 YAHOO.lang.JSON = (function () {
18 * Replace certain Unicode characters that JavaScript may handle incorrectly
19 * during eval--either by deleting them or treating them as line
20 * endings--with escape sequences.
21 * IMPORTANT NOTE: This regex will be used to modify the input if a match is
23 * @property _UNICODE_EXCEPTIONS
27 _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
30 * First step in the validation. Regex used to replace all escape
31 * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
37 _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
40 * Second step in the validation. Regex used to replace all simple
41 * values with ']' characters.
47 _VALUES = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
50 * Third step in the validation. Regex used to remove all open square
51 * brackets following a colon, comma, or at the beginning of the string.
57 _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g,
60 * Final step in the validation. Regex used to test the string left after
61 * all previous replacements for invalid characters.
67 _INVALID = /^[\],:{}\s]*$/,
70 * Regex used to replace special characters in strings for JSON
72 * @property _SPECIAL_CHARS
77 _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
80 * Character substitution map for common escapes and special characters.
97 * Traverses nested objects, applying a filter or reviver function to
98 * each value. The value returned from the function will replace the
99 * original value in the key:value pair. If the value returned is
100 * undefined, the key will be omitted from the returned object.
102 * @param data {MIXED} Any JavaScript data
103 * @param reviver {Function} filter or mutation function
104 * @return {MIXED} The results of the filtered/mutated data structure
107 function _revive(data, reviver) {
108 var walk = function (o,key) {
109 var k,v,value = o[key];
110 if (value && typeof value === 'object') {
112 if (l.hasOwnProperty(value,k)) {
114 if (v === undefined) {
122 return reviver.call(o,key,value);
125 return typeof reviver === 'function' ? walk({'':data},'') : data;
129 * Escapes a special character to a safe Unicode representation
131 * @param c {String} single character to escape
132 * @return {String} safe Unicode escape
136 _CHARS[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
142 * Replace certain Unicode characters that may be handled incorrectly by
143 * some browser implementations.
145 * @param s {String} parse input
146 * @return {String} sanitized JSON string ready to be validated/parsed
149 function _prepare(s) {
150 return s.replace(_UNICODE_EXCEPTIONS, _char);
154 * Four step determination whether a string is valid JSON. In three steps,
155 * escape sequences, safe values, and properly placed open square brackets
156 * are replaced with placeholders or removed. Then in the final step, the
157 * result of all these replacements is checked for invalid characters.
159 * @param str {String} JSON string to be tested
160 * @return {boolean} is the string safe for eval?
163 function _isValid(str) {
164 return l.isString(str) &&
166 replace(_ESCAPES,'@').
167 replace(_VALUES,']').
168 replace(_BRACKETS,''));
172 * Enclose escaped strings in quotes
174 * @param s {String} string to wrap
175 * @return {String} '"'+s+'"' after s has had special characters escaped
178 function _string(s) {
179 return '"' + s.replace(_SPECIAL_CHARS, _char) + '"';
183 * Worker function used by public stringify.
185 * @param h {Object} object holding the key
186 * @param key {String} String key in object h to serialize
187 * @param depth {Number} depth to serialize
188 * @param w {Array|Function} array of whitelisted keys OR replacer function
189 * @param pstack {Array} used to protect against recursion
190 * @return {String} serialized version of o
192 function _stringify(h,key,d,w,pstack) {
193 var o = typeof w === 'function' ? w.call(h,key,h[key]) : h[key],
194 i,len,j, // array iteration
195 k,v, // object iteration
196 isArray, // forking in typeof 'object'
197 a; // composition array for performance over string concat
199 if (o instanceof Date) {
200 o = l.JSON.dateToString(o);
201 } else if (o instanceof String || o instanceof Boolean || o instanceof Number) {
206 case 'string' : return _string(o);
207 case 'number' : return isFinite(o) ? String(o) : 'null';
208 case 'boolean': return String(o);
215 // Check for cyclical references
216 for (i = pstack.length - 1; i >= 0; --i) {
217 if (pstack[i] === o) {
222 // Add the object to the processing stack
223 pstack[pstack.length] = o;
226 isArray = l.isArray(o);
228 // Only recurse if we're above depth config
232 for (i = o.length - 1; i >= 0; --i) {
233 a[i] = _stringify(o,i,d-1,w,pstack) || 'null';
239 // Use whitelist keys if provided as an array
241 for (i = 0, len = w.length; i < len; ++i) {
243 v = _stringify(o,k,d-1,w,pstack);
245 a[j++] = _string(k) + ':' + v;
250 if (typeof k === 'string' && l.hasOwnProperty(o,k)) {
251 v = _stringify(o,k,d-1,w,pstack);
253 a[j++] = _string(k) + ':' + v;
259 // sort object keys for easier readability
264 // remove the object from the stack
267 return isArray ? '['+a.join(',')+']' : '{'+a.join(',')+'}';
270 return undefined; // invalid input
273 // Return the public API
276 * Four step determination whether a string is valid JSON. In three steps,
277 * escape sequences, safe values, and properly placed open square brackets
278 * are replaced with placeholders or removed. Then in the final step, the
279 * result of all these replacements is checked for invalid characters.
281 * @param str {String} JSON string to be tested
282 * @return {boolean} is the string safe for eval?
285 isValid : function (s) {
286 return _isValid(_prepare(s));
290 * Parse a JSON string, returning the native JavaScript representation.
291 * Only minor modifications from http://www.json.org/json2.js.
292 * @param s {string} JSON string data
293 * @param reviver {function} (optional) function(k,v) passed each key:value
294 * pair of object literals, allowing pruning or altering values
295 * @return {MIXED} the native JavaScript representation of the JSON string
296 * @throws SyntaxError
300 parse : function (s,reviver) {
306 // Eval the text into a JavaScript data structure, apply the
307 // reviver function if provided, and return
308 return _revive( eval('(' + s + ')'), reviver );
311 // The text is not valid JSON
312 throw new SyntaxError('parseJSON');
316 * Converts an arbitrary value to a JSON string representation.
317 * Cyclical object or array references are replaced with null.
318 * If a whitelist is provided, only matching object keys will be included.
319 * If a depth limit is provided, objects and arrays at that depth will
320 * be stringified as empty.
322 * @param o {MIXED} any arbitrary object to convert to JSON string
323 * @param w {Array|Function} (optional) whitelist of acceptable object keys to include OR a function(value,key) to alter values before serialization
324 * @param d {number} (optional) depth limit to recurse objects/arrays (practical minimum 1)
325 * @return {string} JSON string representation of the input
328 stringify : function (o,w,d) {
329 if (o !== undefined) {
330 // Ensure whitelist keys are unique (bug 2110391)
333 var uniq=[],map={},v,i,j,len;
334 for (i=0,j=0,len=a.length; i<len; ++i) {
336 if (typeof v === 'string' && map[v] === undefined) {
337 uniq[(map[v] = j++)] = v;
344 // Default depth to POSITIVE_INFINITY
345 d = d >= 0 ? d : 1/0;
348 return _stringify({'':o},'',d,w,[]);
355 * Serializes a Date instance as a UTC date string. Used internally by
356 * stringify. Override this method if you need Dates serialized in a
358 * @method dateToString
359 * @param d {Date} The Date to serialize
360 * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
363 dateToString : function (d) {
364 function _zeroPad(v) {
365 return v < 10 ? '0' + v : v;
368 return d.getUTCFullYear() + '-' +
369 _zeroPad(d.getUTCMonth() + 1) + '-' +
370 _zeroPad(d.getUTCDate()) + 'T' +
371 _zeroPad(d.getUTCHours()) + ':' +
372 _zeroPad(d.getUTCMinutes()) + ':' +
373 _zeroPad(d.getUTCSeconds()) + 'Z';
377 * Reconstitute Date instances from the default JSON UTC serialization.
378 * Reference this from a reviver function to rebuild Dates during the
380 * @method stringToDate
381 * @param str {String} String serialization of a Date
384 stringToDate : function (str) {
385 if (/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.test(str)) {
387 d.setUTCFullYear(RegExp.$1, (RegExp.$2|0)-1, RegExp.$3);
388 d.setUTCHours(RegExp.$4, RegExp.$5, RegExp.$6);
396 YAHOO.register("json", YAHOO.lang.JSON, {version: "2.7.0", build: "1799"});