src/js/libs/json2.js
changeset 1057 3f20f286d43e
parent 917 eb8677d3a663
equal deleted inserted replaced
1056:3d19cbe56fcb 1057:3f20f286d43e
     1 /*
     1 /*
     2     json2.js
     2     json2.js
     3     2011-10-19
     3     2015-05-03
     4 
     4 
     5     Public Domain.
     5     Public Domain.
     6 
     6 
     7     NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
     7     NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
     8 
     8 
    15     USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    15     USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    16     NOT CONTROL.
    16     NOT CONTROL.
    17 
    17 
    18 
    18 
    19     This file creates a global JSON object containing two methods: stringify
    19     This file creates a global JSON object containing two methods: stringify
    20     and parse.
    20     and parse. This file is provides the ES5 JSON capability to ES3 systems.
       
    21     If a project might run on IE8 or earlier, then this file should be included.
       
    22     This file does nothing on ES5 systems.
    21 
    23 
    22         JSON.stringify(value, replacer, space)
    24         JSON.stringify(value, replacer, space)
    23             value       any JavaScript value, usually an object or array.
    25             value       any JavaScript value, usually an object or array.
    24 
    26 
    25             replacer    an optional parameter that determines how object
    27             replacer    an optional parameter that determines how object
    46             For example, this would serialize Dates as ISO strings.
    48             For example, this would serialize Dates as ISO strings.
    47 
    49 
    48                 Date.prototype.toJSON = function (key) {
    50                 Date.prototype.toJSON = function (key) {
    49                     function f(n) {
    51                     function f(n) {
    50                         // Format integers to have at least two digits.
    52                         // Format integers to have at least two digits.
    51                         return n < 10 ? '0' + n : n;
    53                         return n < 10 
       
    54                             ? '0' + n 
       
    55                             : n;
    52                     }
    56                     }
    53 
    57 
    54                     return this.getUTCFullYear()   + '-' +
    58                     return this.getUTCFullYear()   + '-' +
    55                          f(this.getUTCMonth() + 1) + '-' +
    59                          f(this.getUTCMonth() + 1) + '-' +
    56                          f(this.getUTCDate())      + 'T' +
    60                          f(this.getUTCDate())      + 'T' +
    92 
    96 
    93             text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
    97             text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
    94             // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
    98             // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
    95 
    99 
    96             text = JSON.stringify([new Date()], function (key, value) {
   100             text = JSON.stringify([new Date()], function (key, value) {
    97                 return this[key] instanceof Date ?
   101                 return this[key] instanceof Date 
    98                     'Date(' + this[key] + ')' : value;
   102                     ? 'Date(' + this[key] + ')' 
       
   103                     : value;
    99             });
   104             });
   100             // text is '["Date(---current time---)"]'
   105             // text is '["Date(---current time---)"]'
   101 
   106 
   102 
   107 
   103         JSON.parse(text, reviver)
   108         JSON.parse(text, reviver)
   144 
   149 
   145     This is a reference implementation. You are free to copy, modify, or
   150     This is a reference implementation. You are free to copy, modify, or
   146     redistribute.
   151     redistribute.
   147 */
   152 */
   148 
   153 
   149 /*jslint evil: true, regexp: true */
   154 /*jslint 
   150 
   155     eval, for, this 
   151 /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
   156 */
   152     call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
   157 
       
   158 /*property
       
   159     JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
   153     getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
   160     getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
   154     lastIndex, length, parse, prototype, push, replace, slice, stringify,
   161     lastIndex, length, parse, prototype, push, replace, slice, stringify,
   155     test, toJSON, toString, valueOf
   162     test, toJSON, toString, valueOf
   156 */
   163 */
   157 
   164 
   158 
   165 
   159 // Create a JSON object only if one does not already exist. We create the
   166 // Create a JSON object only if one does not already exist. We create the
   160 // methods in a closure to avoid creating global variables.
   167 // methods in a closure to avoid creating global variables.
   161 
   168 
   162 var JSON;
   169 if (typeof JSON !== 'object') {
   163 if (!JSON) {
       
   164     JSON = {};
   170     JSON = {};
   165 }
   171 }
   166 
   172 
   167 (function () {
   173 (function () {
   168     'use strict';
   174     'use strict';
       
   175     
       
   176     var rx_one = /^[\],:{}\s]*$/,
       
   177         rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
       
   178         rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
       
   179         rx_four = /(?:^|:|,)(?:\s*\[)+/g,
       
   180         rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
       
   181         rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
   169 
   182 
   170     function f(n) {
   183     function f(n) {
   171         // Format integers to have at least two digits.
   184         // Format integers to have at least two digits.
   172         return n < 10 ? '0' + n : n;
   185         return n < 10 
       
   186             ? '0' + n 
       
   187             : n;
       
   188     }
       
   189     
       
   190     function this_value() {
       
   191         return this.valueOf();
   173     }
   192     }
   174 
   193 
   175     if (typeof Date.prototype.toJSON !== 'function') {
   194     if (typeof Date.prototype.toJSON !== 'function') {
   176 
   195 
   177         Date.prototype.toJSON = function (key) {
   196         Date.prototype.toJSON = function () {
   178 
   197 
   179             return isFinite(this.valueOf())
   198             return isFinite(this.valueOf())
   180                 ? this.getUTCFullYear()     + '-' +
   199                 ? this.getUTCFullYear() + '-' +
   181                     f(this.getUTCMonth() + 1) + '-' +
   200                         f(this.getUTCMonth() + 1) + '-' +
   182                     f(this.getUTCDate())      + 'T' +
   201                         f(this.getUTCDate()) + 'T' +
   183                     f(this.getUTCHours())     + ':' +
   202                         f(this.getUTCHours()) + ':' +
   184                     f(this.getUTCMinutes())   + ':' +
   203                         f(this.getUTCMinutes()) + ':' +
   185                     f(this.getUTCSeconds())   + 'Z'
   204                         f(this.getUTCSeconds()) + 'Z'
   186                 : null;
   205                 : null;
   187         };
   206         };
   188 
   207 
   189         String.prototype.toJSON      =
   208         Boolean.prototype.toJSON = this_value;
   190             Number.prototype.toJSON  =
   209         Number.prototype.toJSON = this_value;
   191             Boolean.prototype.toJSON = function (key) {
   210         String.prototype.toJSON = this_value;
   192                 return this.valueOf();
   211     }
   193             };
   212 
   194     }
   213     var gap,
   195 
       
   196     var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
       
   197         escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
       
   198         gap,
       
   199         indent,
   214         indent,
   200         meta = {    // table of character substitutions
   215         meta,
   201             '\b': '\\b',
       
   202             '\t': '\\t',
       
   203             '\n': '\\n',
       
   204             '\f': '\\f',
       
   205             '\r': '\\r',
       
   206             '"' : '\\"',
       
   207             '\\': '\\\\'
       
   208         },
       
   209         rep;
   216         rep;
   210 
   217 
   211 
   218 
   212     function quote(string) {
   219     function quote(string) {
   213 
   220 
   214 // If the string contains no control characters, no quote characters, and no
   221 // If the string contains no control characters, no quote characters, and no
   215 // backslash characters, then we can safely slap some quotes around it.
   222 // backslash characters, then we can safely slap some quotes around it.
   216 // Otherwise we must also replace the offending characters with safe escape
   223 // Otherwise we must also replace the offending characters with safe escape
   217 // sequences.
   224 // sequences.
   218 
   225 
   219         escapable.lastIndex = 0;
   226         rx_escapable.lastIndex = 0;
   220         return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
   227         return rx_escapable.test(string) 
   221             var c = meta[a];
   228             ? '"' + string.replace(rx_escapable, function (a) {
   222             return typeof c === 'string'
   229                 var c = meta[a];
   223                 ? c
   230                 return typeof c === 'string'
   224                 : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
   231                     ? c
   225         }) + '"' : '"' + string + '"';
   232                     : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
       
   233             }) + '"' 
       
   234             : '"' + string + '"';
   226     }
   235     }
   227 
   236 
   228 
   237 
   229     function str(key, holder) {
   238     function str(key, holder) {
   230 
   239 
   260 
   269 
   261         case 'number':
   270         case 'number':
   262 
   271 
   263 // JSON numbers must be finite. Encode non-finite numbers as null.
   272 // JSON numbers must be finite. Encode non-finite numbers as null.
   264 
   273 
   265             return isFinite(value) ? String(value) : 'null';
   274             return isFinite(value) 
       
   275                 ? String(value) 
       
   276                 : 'null';
   266 
   277 
   267         case 'boolean':
   278         case 'boolean':
   268         case 'null':
   279         case 'null':
   269 
   280 
   270 // If the value is a boolean or null, convert it to a string. Note:
   281 // If the value is a boolean or null, convert it to a string. Note:
   306 // brackets.
   317 // brackets.
   307 
   318 
   308                 v = partial.length === 0
   319                 v = partial.length === 0
   309                     ? '[]'
   320                     ? '[]'
   310                     : gap
   321                     : gap
   311                     ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
   322                         ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
   312                     : '[' + partial.join(',') + ']';
   323                         : '[' + partial.join(',') + ']';
   313                 gap = mind;
   324                 gap = mind;
   314                 return v;
   325                 return v;
   315             }
   326             }
   316 
   327 
   317 // If the replacer is an array, use it to select the members to be stringified.
   328 // If the replacer is an array, use it to select the members to be stringified.
   321                 for (i = 0; i < length; i += 1) {
   332                 for (i = 0; i < length; i += 1) {
   322                     if (typeof rep[i] === 'string') {
   333                     if (typeof rep[i] === 'string') {
   323                         k = rep[i];
   334                         k = rep[i];
   324                         v = str(k, value);
   335                         v = str(k, value);
   325                         if (v) {
   336                         if (v) {
   326                             partial.push(quote(k) + (gap ? ': ' : ':') + v);
   337                             partial.push(quote(k) + (
       
   338                                 gap 
       
   339                                     ? ': ' 
       
   340                                     : ':'
       
   341                             ) + v);
   327                         }
   342                         }
   328                     }
   343                     }
   329                 }
   344                 }
   330             } else {
   345             } else {
   331 
   346 
   333 
   348 
   334                 for (k in value) {
   349                 for (k in value) {
   335                     if (Object.prototype.hasOwnProperty.call(value, k)) {
   350                     if (Object.prototype.hasOwnProperty.call(value, k)) {
   336                         v = str(k, value);
   351                         v = str(k, value);
   337                         if (v) {
   352                         if (v) {
   338                             partial.push(quote(k) + (gap ? ': ' : ':') + v);
   353                             partial.push(quote(k) + (
       
   354                                 gap 
       
   355                                     ? ': ' 
       
   356                                     : ':'
       
   357                             ) + v);
   339                         }
   358                         }
   340                     }
   359                     }
   341                 }
   360                 }
   342             }
   361             }
   343 
   362 
   345 // and wrap them in braces.
   364 // and wrap them in braces.
   346 
   365 
   347             v = partial.length === 0
   366             v = partial.length === 0
   348                 ? '{}'
   367                 ? '{}'
   349                 : gap
   368                 : gap
   350                 ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
   369                     ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
   351                 : '{' + partial.join(',') + '}';
   370                     : '{' + partial.join(',') + '}';
   352             gap = mind;
   371             gap = mind;
   353             return v;
   372             return v;
   354         }
   373         }
   355     }
   374     }
   356 
   375 
   357 // If the JSON object does not yet have a stringify method, give it one.
   376 // If the JSON object does not yet have a stringify method, give it one.
   358 
   377 
   359     if (typeof JSON.stringify !== 'function') {
   378     if (typeof JSON.stringify !== 'function') {
       
   379         meta = {    // table of character substitutions
       
   380             '\b': '\\b',
       
   381             '\t': '\\t',
       
   382             '\n': '\\n',
       
   383             '\f': '\\f',
       
   384             '\r': '\\r',
       
   385             '"': '\\"',
       
   386             '\\': '\\\\'
       
   387         };
   360         JSON.stringify = function (value, replacer, space) {
   388         JSON.stringify = function (value, replacer, space) {
   361 
   389 
   362 // The stringify method takes a value and an optional replacer, and an optional
   390 // The stringify method takes a value and an optional replacer, and an optional
   363 // space parameter, and returns a JSON text. The replacer can be a function
   391 // space parameter, and returns a JSON text. The replacer can be a function
   364 // that can replace values, or an array of strings that will select the keys.
   392 // that can replace values, or an array of strings that will select the keys.
   436 // Parsing happens in four stages. In the first stage, we replace certain
   464 // Parsing happens in four stages. In the first stage, we replace certain
   437 // Unicode characters with escape sequences. JavaScript handles many characters
   465 // Unicode characters with escape sequences. JavaScript handles many characters
   438 // incorrectly, either silently deleting them, or treating them as line endings.
   466 // incorrectly, either silently deleting them, or treating them as line endings.
   439 
   467 
   440             text = String(text);
   468             text = String(text);
   441             cx.lastIndex = 0;
   469             rx_dangerous.lastIndex = 0;
   442             if (cx.test(text)) {
   470             if (rx_dangerous.test(text)) {
   443                 text = text.replace(cx, function (a) {
   471                 text = text.replace(rx_dangerous, function (a) {
   444                     return '\\u' +
   472                     return '\\u' +
   445                         ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
   473                             ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
   446                 });
   474                 });
   447             }
   475             }
   448 
   476 
   449 // In the second stage, we run the text against regular expressions that look
   477 // In the second stage, we run the text against regular expressions that look
   450 // for non-JSON patterns. We are especially concerned with '()' and 'new'
   478 // for non-JSON patterns. We are especially concerned with '()' and 'new'
   457 // replace all simple value tokens with ']' characters. Third, we delete all
   485 // replace all simple value tokens with ']' characters. Third, we delete all
   458 // open brackets that follow a colon or comma or that begin the text. Finally,
   486 // open brackets that follow a colon or comma or that begin the text. Finally,
   459 // we look to see that the remaining characters are only whitespace or ']' or
   487 // we look to see that the remaining characters are only whitespace or ']' or
   460 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
   488 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
   461 
   489 
   462             if (/^[\],:{}\s]*$/
   490             if (
   463                     .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
   491                 rx_one.test(
   464                         .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
   492                     text
   465                         .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
   493                         .replace(rx_two, '@')
       
   494                         .replace(rx_three, ']')
       
   495                         .replace(rx_four, '')
       
   496                 )
       
   497             ) {
   466 
   498 
   467 // In the third stage we use the eval function to compile the text into a
   499 // In the third stage we use the eval function to compile the text into a
   468 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
   500 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
   469 // in JavaScript: it can begin a block or an object literal. We wrap the text
   501 // in JavaScript: it can begin a block or an object literal. We wrap the text
   470 // in parens to eliminate the ambiguity.
   502 // in parens to eliminate the ambiguity.