annotate awesome_js/json2.js @ 45:bff96abdddfa laserkard

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