1 /** 2 * @fileOverview Douglas Crockford’s JSON parser and serializer. 3 */ 4 5 /** 6 * @name JSON 7 * @namespace 8 */ 9 10 /** 11 * This method produces a JSON text from a JavaScript value. 12 * @name JSON.stringify 13 * @function 14 * @param {Object} value Any JavaScript value, usually an object or array. 15 * @param {Function|String[]} [replacer] An optional parameter that determines how object 16 * values are stringified for objects. It can be a 17 * function or an array of strings. 18 * @param {String|Number} [space] An optional parameter that specifies the indentation 19 * of nested structures. If it is omitted, the text will 20 * be packed without extra whitespace. If it is a number, 21 * it will specify the number of spaces to indent at each 22 * level. If it is a string (such as '\t' or ' '), 23 * it contains the characters used to indent at each level. 24 * @returns {String} 25 */ 26 27 /** 28 * This method parses a JSON text to produce an object or array. 29 * @name JSON.parse 30 * @function 31 * @param {String} text The JSON text. 32 * @param {Function} reviver A function that can filter and 33 * transform the results. It receives each of the keys and values, 34 * and its return value is used instead of the original value. 35 * If it returns what it received, then the structure is not modified. 36 * If it returns undefined then the member is deleted. 37 * @returns {Object} 38 */ 39 40 41 /* 42 http://www.JSON.org/json2.js 43 2011-02-23 44 45 Public Domain. 46 47 NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 48 49 See http://www.JSON.org/js.html 50 51 52 This code should be minified before deployment. 53 See http://javascript.crockford.com/jsmin.html 54 55 USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 56 NOT CONTROL. 57 58 59 This file creates a global JSON object containing two methods: stringify 60 and parse. 61 62 JSON.stringify(value, replacer, space) 63 value any JavaScript value, usually an object or array. 64 65 replacer an optional parameter that determines how object 66 values are stringified for objects. It can be a 67 function or an array of strings. 68 69 space an optional parameter that specifies the indentation 70 of nested structures. If it is omitted, the text will 71 be packed without extra whitespace. If it is a number, 72 it will specify the number of spaces to indent at each 73 level. If it is a string (such as '\t' or ' '), 74 it contains the characters used to indent at each level. 75 76 This method produces a JSON text from a JavaScript value. 77 78 When an object value is found, if the object contains a toJSON 79 method, its toJSON method will be called and the result will be 80 stringified. A toJSON method does not serialize: it returns the 81 value represented by the name/value pair that should be serialized, 82 or undefined if nothing should be serialized. The toJSON method 83 will be passed the key associated with the value, and this will be 84 bound to the value 85 86 For example, this would serialize Dates as ISO strings. 87 88 Date.prototype.toJSON = function (key) { 89 function f(n) { 90 // Format integers to have at least two digits. 91 return n < 10 ? '0' + n : n; 92 } 93 94 return this.getUTCFullYear() + '-' + 95 f(this.getUTCMonth() + 1) + '-' + 96 f(this.getUTCDate()) + 'T' + 97 f(this.getUTCHours()) + ':' + 98 f(this.getUTCMinutes()) + ':' + 99 f(this.getUTCSeconds()) + 'Z'; 100 }; 101 102 You can provide an optional replacer method. It will be passed the 103 key and value of each member, with this bound to the containing 104 object. The value that is returned from your method will be 105 serialized. If your method returns undefined, then the member will 106 be excluded from the serialization. 107 108 If the replacer parameter is an array of strings, then it will be 109 used to select the members to be serialized. It filters the results 110 such that only members with keys listed in the replacer array are 111 stringified. 112 113 Values that do not have JSON representations, such as undefined or 114 functions, will not be serialized. Such values in objects will be 115 dropped; in arrays they will be replaced with null. You can use 116 a replacer function to replace those with JSON values. 117 JSON.stringify(undefined) returns undefined. 118 119 The optional space parameter produces a stringification of the 120 value that is filled with line breaks and indentation to make it 121 easier to read. 122 123 If the space parameter is a non-empty string, then that string will 124 be used for indentation. If the space parameter is a number, then 125 the indentation will be that many spaces. 126 127 Example: 128 129 text = JSON.stringify(['e', {pluribus: 'unum'}]); 130 // text is '["e",{"pluribus":"unum"}]' 131 132 133 text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 134 // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 135 136 text = JSON.stringify([new Date()], function (key, value) { 137 return this[key] instanceof Date ? 138 'Date(' + this[key] + ')' : value; 139 }); 140 // text is '["Date(---current time---)"]' 141 142 143 JSON.parse(text, reviver) 144 This method parses a JSON text to produce an object or array. 145 It can throw a SyntaxError exception. 146 147 The optional reviver parameter is a function that can filter and 148 transform the results. It receives each of the keys and values, 149 and its return value is used instead of the original value. 150 If it returns what it received, then the structure is not modified. 151 If it returns undefined then the member is deleted. 152 153 Example: 154 155 // Parse the text. Values that look like ISO date strings will 156 // be converted to Date objects. 157 158 myData = JSON.parse(text, function (key, value) { 159 var a; 160 if (typeof value === 'string') { 161 a = 162 /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 163 if (a) { 164 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 165 +a[5], +a[6])); 166 } 167 } 168 return value; 169 }); 170 171 myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 172 var d; 173 if (typeof value === 'string' && 174 value.slice(0, 5) === 'Date(' && 175 value.slice(-1) === ')') { 176 d = new Date(value.slice(5, -1)); 177 if (d) { 178 return d; 179 } 180 } 181 return value; 182 }); 183 184 185 This is a reference implementation. You are free to copy, modify, or 186 redistribute. 187 */ 188 189 /*jslint evil: true, strict: false, regexp: false */ 190 191 /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 192 call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 193 getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 194 lastIndex, length, parse, prototype, push, replace, slice, stringify, 195 test, toJSON, toString, valueOf 196 */ 197 198 199 // Create a JSON object only if one does not already exist. We create the 200 // methods in a closure to avoid creating global variables. 201 202 var JSON; 203 if (!JSON) { 204 JSON = {}; 205 } 206 207 (function () { 208 "use strict"; 209 210 function f(n) { 211 // Format integers to have at least two digits. 212 return n < 10 ? '0' + n : n; 213 } 214 215 if (typeof Date.prototype.toJSON !== 'function') { 216 217 Date.prototype.toJSON = function (key) { 218 219 return isFinite(this.valueOf()) ? 220 this.getUTCFullYear() + '-' + 221 f(this.getUTCMonth() + 1) + '-' + 222 f(this.getUTCDate()) + 'T' + 223 f(this.getUTCHours()) + ':' + 224 f(this.getUTCMinutes()) + ':' + 225 f(this.getUTCSeconds()) + 'Z' : null; 226 }; 227 228 String.prototype.toJSON = 229 Number.prototype.toJSON = 230 Boolean.prototype.toJSON = function (key) { 231 return this.valueOf(); 232 }; 233 } 234 235 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 236 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 237 gap, 238 indent, 239 meta = { // table of character substitutions 240 '\b': '\\b', 241 '\t': '\\t', 242 '\n': '\\n', 243 '\f': '\\f', 244 '\r': '\\r', 245 '"' : '\\"', 246 '\\': '\\\\' 247 }, 248 rep; 249 250 251 function quote(string) { 252 253 // If the string contains no control characters, no quote characters, and no 254 // backslash characters, then we can safely slap some quotes around it. 255 // Otherwise we must also replace the offending characters with safe escape 256 // sequences. 257 258 escapable.lastIndex = 0; 259 return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 260 var c = meta[a]; 261 return typeof c === 'string' ? c : 262 '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 263 }) + '"' : '"' + string + '"'; 264 } 265 266 267 function str(key, holder) { 268 269 // Produce a string from holder[key]. 270 271 var i, // The loop counter. 272 k, // The member key. 273 v, // The member value. 274 length, 275 mind = gap, 276 partial, 277 value = holder[key]; 278 279 // If the value has a toJSON method, call it to obtain a replacement value. 280 281 if (value && typeof value === 'object' && 282 typeof value.toJSON === 'function') { 283 value = value.toJSON(key); 284 } 285 286 // If we were called with a replacer function, then call the replacer to 287 // obtain a replacement value. 288 289 if (typeof rep === 'function') { 290 value = rep.call(holder, key, value); 291 } 292 293 // What happens next depends on the value's type. 294 295 switch (typeof value) { 296 case 'string': 297 return quote(value); 298 299 case 'number': 300 301 // JSON numbers must be finite. Encode non-finite numbers as null. 302 303 return isFinite(value) ? String(value) : 'null'; 304 305 case 'boolean': 306 case 'null': 307 308 // If the value is a boolean or null, convert it to a string. Note: 309 // typeof null does not produce 'null'. The case is included here in 310 // the remote chance that this gets fixed someday. 311 312 return String(value); 313 314 // If the type is 'object', we might be dealing with an object or an array or 315 // null. 316 317 case 'object': 318 319 // Due to a specification blunder in ECMAScript, typeof null is 'object', 320 // so watch out for that case. 321 322 if (!value) { 323 return 'null'; 324 } 325 326 // Make an array to hold the partial results of stringifying this object value. 327 328 gap += indent; 329 partial = []; 330 331 // Is the value an array? 332 333 if (Object.prototype.toString.apply(value) === '[object Array]') { 334 335 // The value is an array. Stringify every element. Use null as a placeholder 336 // for non-JSON values. 337 338 length = value.length; 339 for (i = 0; i < length; i += 1) { 340 partial[i] = str(i, value) || 'null'; 341 } 342 343 // Join all of the elements together, separated with commas, and wrap them in 344 // brackets. 345 346 v = partial.length === 0 ? '[]' : gap ? 347 '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 348 '[' + partial.join(',') + ']'; 349 gap = mind; 350 return v; 351 } 352 353 // If the replacer is an array, use it to select the members to be stringified. 354 355 if (rep && typeof rep === 'object') { 356 length = rep.length; 357 for (i = 0; i < length; i += 1) { 358 if (typeof rep[i] === 'string') { 359 k = rep[i]; 360 v = str(k, value); 361 if (v) { 362 partial.push(quote(k) + (gap ? ': ' : ':') + v); 363 } 364 } 365 } 366 } else { 367 368 // Otherwise, iterate through all of the keys in the object. 369 370 for (k in value) { 371 if (Object.prototype.hasOwnProperty.call(value, k)) { 372 v = str(k, value); 373 if (v) { 374 partial.push(quote(k) + (gap ? ': ' : ':') + v); 375 } 376 } 377 } 378 } 379 380 // Join all of the member texts together, separated with commas, 381 // and wrap them in braces. 382 383 v = partial.length === 0 ? '{}' : gap ? 384 '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 385 '{' + partial.join(',') + '}'; 386 gap = mind; 387 return v; 388 } 389 } 390 391 // If the JSON object does not yet have a stringify method, give it one. 392 393 if (typeof JSON.stringify !== 'function') { 394 JSON.stringify = function (value, replacer, space) { 395 396 // The stringify method takes a value and an optional replacer, and an optional 397 // space parameter, and returns a JSON text. The replacer can be a function 398 // that can replace values, or an array of strings that will select the keys. 399 // A default replacer method can be provided. Use of the space parameter can 400 // produce text that is more easily readable. 401 402 var i; 403 gap = ''; 404 indent = ''; 405 406 // If the space parameter is a number, make an indent string containing that 407 // many spaces. 408 409 if (typeof space === 'number') { 410 for (i = 0; i < space; i += 1) { 411 indent += ' '; 412 } 413 414 // If the space parameter is a string, it will be used as the indent string. 415 416 } else if (typeof space === 'string') { 417 indent = space; 418 } 419 420 // If there is a replacer, it must be a function or an array. 421 // Otherwise, throw an error. 422 423 rep = replacer; 424 if (replacer && typeof replacer !== 'function' && 425 (typeof replacer !== 'object' || 426 typeof replacer.length !== 'number')) { 427 throw new Error('JSON.stringify'); 428 } 429 430 // Make a fake root object containing our value under the key of ''. 431 // Return the result of stringifying the value. 432 433 return str('', {'': value}); 434 }; 435 } 436 437 438 // If the JSON object does not yet have a parse method, give it one. 439 440 if (typeof JSON.parse !== 'function') { 441 JSON.parse = function (text, reviver) { 442 443 // The parse method takes a text and an optional reviver function, and returns 444 // a JavaScript value if the text is a valid JSON text. 445 446 var j; 447 448 function walk(holder, key) { 449 450 // The walk method is used to recursively walk the resulting structure so 451 // that modifications can be made. 452 453 var k, v, value = holder[key]; 454 if (value && typeof value === 'object') { 455 for (k in value) { 456 if (Object.prototype.hasOwnProperty.call(value, k)) { 457 v = walk(value, k); 458 if (v !== undefined) { 459 value[k] = v; 460 } else { 461 delete value[k]; 462 } 463 } 464 } 465 } 466 return reviver.call(holder, key, value); 467 } 468 469 470 // Parsing happens in four stages. In the first stage, we replace certain 471 // Unicode characters with escape sequences. JavaScript handles many characters 472 // incorrectly, either silently deleting them, or treating them as line endings. 473 474 text = String(text); 475 cx.lastIndex = 0; 476 if (cx.test(text)) { 477 text = text.replace(cx, function (a) { 478 return '\\u' + 479 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 480 }); 481 } 482 483 // In the second stage, we run the text against regular expressions that look 484 // for non-JSON patterns. We are especially concerned with '()' and 'new' 485 // because they can cause invocation, and '=' because it can cause mutation. 486 // But just to be safe, we want to reject all unexpected forms. 487 488 // We split the second stage into 4 regexp operations in order to work around 489 // crippling inefficiencies in IE's and Safari's regexp engines. First we 490 // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 491 // replace all simple value tokens with ']' characters. Third, we delete all 492 // open brackets that follow a colon or comma or that begin the text. Finally, 493 // we look to see that the remaining characters are only whitespace or ']' or 494 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 495 496 if (/^[\],:{}\s]*$/ 497 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 498 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 499 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 500 501 // In the third stage we use the eval function to compile the text into a 502 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 503 // in JavaScript: it can begin a block or an object literal. We wrap the text 504 // in parens to eliminate the ambiguity. 505 506 j = eval('(' + text + ')'); 507 508 // In the optional fourth stage, we recursively walk the new structure, passing 509 // each name/value pair to a reviver function for possible transformation. 510 511 return typeof reviver === 'function' ? 512 walk({'': j}, '') : j; 513 } 514 515 // If the text is not JSON parseable, then a SyntaxError is thrown. 516 517 throw new SyntaxError('JSON.parse'); 518 }; 519 } 520 }()); 521 522 // Do not enumerate the new JSON methods. 523 // (These lines are not included in the original code by Crockford.) 524 Object.prototype.dontEnum("toJSONString"); 525 Object.prototype.dontEnum("parseJSON"); 526 527 /** 528 * Create a JSONP-compatible response from the callback name and the desired data. 529 * @param {String} callback The name of the JSONP callback method 530 * @param {Object} data An arbitrary JavaScript object 531 */ 532 JSON.pad = function(data, callback) { 533 if (!callback) { 534 return; 535 } 536 return callback + "(" + JSON.stringify(data) + ")"; 537 } 538 539 /** 540 * Send a JSONP-compatible response if a the request contains callback data. 541 * This works out-of-the-box with jQuery but can be customized using the key argument. 542 * @param {Object} data An arbitrary JavaScript object 543 * @param {String} key The name of the property in req.data containing the JSONP callback method name 544 * @param {Boolean} resume Switch to define whether further processing should be continued or not 545 */ 546 JSON.sendPaddedResponse = function(data, key, resume) { 547 var callback = req.data[key || "callback"]; 548 if (callback) { 549 res.contentType = "text/javascript"; 550 res.write(JSON.pad(data, callback)); 551 resume || res.stop(); 552 } 553 return; 554 } 555