      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