services/sync/tps/extensions/mozmill/resource/stdlib/json2.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

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

mercurial