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