/src/main/resources/org/apache/struts2/static/json/json.js
JavaScript | 396 lines | 328 code | 7 blank | 61 comment | 0 complexity | 0ebd899673df1d2177739ebc0094c831 MD5 | raw file
1/* 2Copyright (c) 2009, Yahoo! Inc. All rights reserved. 3Code licensed under the BSD License: 4http://developer.yahoo.net/yui/license.txt 5version: 2.7.0 6*/ 7/** 8 * Provides methods to parse JSON strings and convert objects to JSON strings. 9 * @module json 10 * @class JSON 11 * @static 12 */ 13YAHOO.lang.JSON = (function () { 14 15var l = YAHOO.lang, 16 17 /** 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 22 * found. 23 * @property _UNICODE_EXCEPTIONS 24 * @type {RegExp} 25 * @private 26 */ 27 _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 28 29 /** 30 * First step in the validation. Regex used to replace all escape 31 * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character). 32 * @property _ESCAPES 33 * @type {RegExp} 34 * @static 35 * @private 36 */ 37 _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, 38 39 /** 40 * Second step in the validation. Regex used to replace all simple 41 * values with ']' characters. 42 * @property _VALUES 43 * @type {RegExp} 44 * @static 45 * @private 46 */ 47 _VALUES = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, 48 49 /** 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. 52 * @property _BRACKETS 53 * @type {RegExp} 54 * @static 55 * @private 56 */ 57 _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g, 58 59 /** 60 * Final step in the validation. Regex used to test the string left after 61 * all previous replacements for invalid characters. 62 * @property _INVALID 63 * @type {RegExp} 64 * @static 65 * @private 66 */ 67 _INVALID = /^[\],:{}\s]*$/, 68 69 /** 70 * Regex used to replace special characters in strings for JSON 71 * stringification. 72 * @property _SPECIAL_CHARS 73 * @type {RegExp} 74 * @static 75 * @private 76 */ 77 _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 78 79 /** 80 * Character substitution map for common escapes and special characters. 81 * @property _CHARS 82 * @type {Object} 83 * @static 84 * @private 85 */ 86 _CHARS = { 87 '\b': '\\b', 88 '\t': '\\t', 89 '\n': '\\n', 90 '\f': '\\f', 91 '\r': '\\r', 92 '"' : '\\"', 93 '\\': '\\\\' 94 }; 95 96/** 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. 101 * @method _revive 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 105 * @private 106 */ 107function _revive(data, reviver) { 108 var walk = function (o,key) { 109 var k,v,value = o[key]; 110 if (value && typeof value === 'object') { 111 for (k in value) { 112 if (l.hasOwnProperty(value,k)) { 113 v = walk(value, k); 114 if (v === undefined) { 115 delete value[k]; 116 } else { 117 value[k] = v; 118 } 119 } 120 } 121 } 122 return reviver.call(o,key,value); 123 }; 124 125 return typeof reviver === 'function' ? walk({'':data},'') : data; 126} 127 128/** 129 * Escapes a special character to a safe Unicode representation 130 * @method _char 131 * @param c {String} single character to escape 132 * @return {String} safe Unicode escape 133 */ 134function _char(c) { 135 if (!_CHARS[c]) { 136 _CHARS[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4); 137 } 138 return _CHARS[c]; 139} 140 141/** 142 * Replace certain Unicode characters that may be handled incorrectly by 143 * some browser implementations. 144 * @method _prepare 145 * @param s {String} parse input 146 * @return {String} sanitized JSON string ready to be validated/parsed 147 * @private 148 */ 149function _prepare(s) { 150 return s.replace(_UNICODE_EXCEPTIONS, _char); 151} 152 153/** 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. 158 * @method _isValid 159 * @param str {String} JSON string to be tested 160 * @return {boolean} is the string safe for eval? 161 * @static 162 */ 163function _isValid(str) { 164 return l.isString(str) && 165 _INVALID.test(str. 166 replace(_ESCAPES,'@'). 167 replace(_VALUES,']'). 168 replace(_BRACKETS,'')); 169} 170 171/** 172 * Enclose escaped strings in quotes 173 * @method _string 174 * @param s {String} string to wrap 175 * @return {String} '"'+s+'"' after s has had special characters escaped 176 * @private 177 */ 178function _string(s) { 179 return '"' + s.replace(_SPECIAL_CHARS, _char) + '"'; 180} 181 182/** 183 * Worker function used by public stringify. 184 * @method _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 191 */ 192function _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 198 199 if (o instanceof Date) { 200 o = l.JSON.dateToString(o); 201 } else if (o instanceof String || o instanceof Boolean || o instanceof Number) { 202 o = o.valueOf(); 203 } 204 205 switch (typeof o) { 206 case 'string' : return _string(o); 207 case 'number' : return isFinite(o) ? String(o) : 'null'; 208 case 'boolean': return String(o); 209 case 'object' : 210 // null 211 if (o === null) { 212 return 'null'; 213 } 214 215 // Check for cyclical references 216 for (i = pstack.length - 1; i >= 0; --i) { 217 if (pstack[i] === o) { 218 return 'null'; 219 } 220 } 221 222 // Add the object to the processing stack 223 pstack[pstack.length] = o; 224 225 a = []; 226 isArray = l.isArray(o); 227 228 // Only recurse if we're above depth config 229 if (d > 0) { 230 // Array 231 if (isArray) { 232 for (i = o.length - 1; i >= 0; --i) { 233 a[i] = _stringify(o,i,d-1,w,pstack) || 'null'; 234 } 235 236 // Object 237 } else { 238 j = 0; 239 // Use whitelist keys if provided as an array 240 if (l.isArray(w)) { 241 for (i = 0, len = w.length; i < len; ++i) { 242 k = w[i]; 243 v = _stringify(o,k,d-1,w,pstack); 244 if (v) { 245 a[j++] = _string(k) + ':' + v; 246 } 247 } 248 } else { 249 for (k in o) { 250 if (typeof k === 'string' && l.hasOwnProperty(o,k)) { 251 v = _stringify(o,k,d-1,w,pstack); 252 if (v) { 253 a[j++] = _string(k) + ':' + v; 254 } 255 } 256 } 257 } 258 259 // sort object keys for easier readability 260 a.sort(); 261 } 262 } 263 264 // remove the object from the stack 265 pstack.pop(); 266 267 return isArray ? '['+a.join(',')+']' : '{'+a.join(',')+'}'; 268 } 269 270 return undefined; // invalid input 271} 272 273// Return the public API 274return { 275 /** 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. 280 * @method isValid 281 * @param str {String} JSON string to be tested 282 * @return {boolean} is the string safe for eval? 283 * @static 284 */ 285 isValid : function (s) { 286 return _isValid(_prepare(s)); 287 }, 288 289 /** 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 297 * @method parse 298 * @static 299 */ 300 parse : function (s,reviver) { 301 // sanitize 302 s = _prepare(s); 303 304 // Ensure valid JSON 305 if (_isValid(s)) { 306 // Eval the text into a JavaScript data structure, apply the 307 // reviver function if provided, and return 308 return _revive( eval('(' + s + ')'), reviver ); 309 } 310 311 // The text is not valid JSON 312 throw new SyntaxError('parseJSON'); 313 }, 314 315 /** 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. 321 * @method stringify 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 326 * @static 327 */ 328 stringify : function (o,w,d) { 329 if (o !== undefined) { 330 // Ensure whitelist keys are unique (bug 2110391) 331 if (l.isArray(w)) { 332 w = (function (a) { 333 var uniq=[],map={},v,i,j,len; 334 for (i=0,j=0,len=a.length; i<len; ++i) { 335 v = a[i]; 336 if (typeof v === 'string' && map[v] === undefined) { 337 uniq[(map[v] = j++)] = v; 338 } 339 } 340 return uniq; 341 })(w); 342 } 343 344 // Default depth to POSITIVE_INFINITY 345 d = d >= 0 ? d : 1/0; 346 347 // process the input 348 return _stringify({'':o},'',d,w,[]); 349 } 350 351 return undefined; 352 }, 353 354 /** 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 357 * different format. 358 * @method dateToString 359 * @param d {Date} The Date to serialize 360 * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ 361 * @static 362 */ 363 dateToString : function (d) { 364 function _zeroPad(v) { 365 return v < 10 ? '0' + v : v; 366 } 367 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'; 374 }, 375 376 /** 377 * Reconstitute Date instances from the default JSON UTC serialization. 378 * Reference this from a reviver function to rebuild Dates during the 379 * parse operation. 380 * @method stringToDate 381 * @param str {String} String serialization of a Date 382 * @return {Date} 383 */ 384 stringToDate : function (str) { 385 if (/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.test(str)) { 386 var d = new Date(); 387 d.setUTCFullYear(RegExp.$1, (RegExp.$2|0)-1, RegExp.$3); 388 d.setUTCHours(RegExp.$4, RegExp.$5, RegExp.$6); 389 return d; 390 } 391 return str; 392 } 393}; 394 395})(); 396YAHOO.register("json", YAHOO.lang.JSON, {version: "2.7.0", build: "1799"});