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