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