annotate js-lib/json2.js @ 90:08f93d043ed2 laserkard

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