]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/json/json.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / yui / build / json / json.js
1 /*
2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 2.9.0
6 */
7 /**
8  * Provides methods to parse JSON strings and convert objects to JSON strings.
9  *
10  * @module json
11  * @class JSON
12  * @namespace YAHOO.lang
13  * @static
14  */
15 (function () {
16
17 var l = YAHOO.lang,
18     isFunction = l.isFunction,
19     isObject   = l.isObject,
20     isArray    = l.isArray,
21     _toStr     = Object.prototype.toString,
22                  // 'this' is the global object.  window in browser env.  Keep
23                  // the code env agnostic.  Caja requies window, unfortunately.
24     Native     = (YAHOO.env.ua.caja ? window : this).JSON,
25
26 /* Variables used by parse */
27
28     /**
29      * Replace certain Unicode characters that JavaScript may handle incorrectly
30      * during eval--either by deleting them or treating them as line
31      * endings--with escape sequences.
32      * IMPORTANT NOTE: This regex will be used to modify the input if a match is
33      * found.
34      *
35      * @property _UNICODE_EXCEPTIONS
36      * @type {RegExp}
37      * @private
38      */
39     _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
40
41     /**
42      * First step in the safety evaluation.  Regex used to replace all escape
43      * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
44      *
45      * @property _ESCAPES
46      * @type {RegExp}
47      * @static
48      * @private
49      */
50     _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
51
52     /**
53      * Second step in the safety evaluation.  Regex used to replace all simple
54      * values with ']' characters.
55      *
56      * @property _VALUES
57      * @type {RegExp}
58      * @static
59      * @private
60      */
61     _VALUES  = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
62
63     /**
64      * Third step in the safety evaluation.  Regex used to remove all open
65      * square brackets following a colon, comma, or at the beginning of the
66      * string.
67      *
68      * @property _BRACKETS
69      * @type {RegExp}
70      * @static
71      * @private
72      */
73     _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g,
74
75     /**
76      * Final step in the safety evaluation.  Regex used to test the string left
77      * after all previous replacements for invalid characters.
78      *
79      * @property _UNSAFE
80      * @type {RegExp}
81      * @static
82      * @private
83      */
84     _UNSAFE  = /[^\],:{}\s]/,
85
86
87 /* Variables used by stringify */
88
89     /**
90      * Regex used to replace special characters in strings for JSON
91      * stringification.
92      *
93      * @property _SPECIAL_CHARS
94      * @type {RegExp}
95      * @static
96      * @private
97      */
98     _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
99
100     /**
101      * Character substitution map for common escapes and special characters.
102      *
103      * @property _CHARS
104      * @type {Object}
105      * @static
106      * @private
107      */
108     _CHARS = {
109         '\b': '\\b',
110         '\t': '\\t',
111         '\n': '\\n',
112         '\f': '\\f',
113         '\r': '\\r',
114         '"' : '\\"',
115         '\\': '\\\\'
116     },
117     
118     UNDEFINED = 'undefined',
119     OBJECT    = 'object',
120     NULL      = 'null',
121     STRING    = 'string',
122     NUMBER    = 'number',
123     BOOLEAN   = 'boolean',
124     DATE      = 'date',
125     _allowable = {
126         'undefined'        : UNDEFINED,
127         'string'           : STRING,
128         '[object String]'  : STRING,
129         'number'           : NUMBER,
130         '[object Number]'  : NUMBER,
131         'boolean'          : BOOLEAN,
132         '[object Boolean]' : BOOLEAN,
133         '[object Date]'    : DATE,
134         '[object RegExp]'  : OBJECT
135     },
136     EMPTY     = '',
137     OPEN_O    = '{',
138     CLOSE_O   = '}',
139     OPEN_A    = '[',
140     CLOSE_A   = ']',
141     COMMA     = ',',
142     COMMA_CR  = ",\n",
143     CR        = "\n",
144     COLON     = ':',
145     COLON_SP  = ': ',
146     QUOTE     = '"';
147
148 // Only accept JSON objects that report a [[Class]] of JSON
149 Native = _toStr.call(Native) === '[object JSON]' && Native;
150
151 // Escapes a special character to a safe Unicode representation
152 function _char(c) {
153     if (!_CHARS[c]) {
154         _CHARS[c] =  '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
155     }
156     return _CHARS[c];
157 }
158
159
160 /* functions used by parse */
161
162 /**
163  * Traverses nested objects, applying a filter or reviver function to
164  * each value.  The value returned from the function will replace the
165  * original value in the key:value pair.  If the value returned is
166  * undefined, the key will be omitted from the returned object.
167  *
168  * @method _revive
169  * @param data {MIXED} Any JavaScript data
170  * @param reviver {Function} filter or mutation function
171  * @return {MIXED} The results of the filtered/mutated data structure
172  * @private
173  */
174 function _revive(data, reviver) {
175     var walk = function (o,key) {
176         var k,v,value = o[key];
177         if (value && typeof value === 'object') {
178             for (k in value) {
179                 if (l.hasOwnProperty(value,k)) {
180                     v = walk(value, k);
181                     if (v === undefined) {
182                         delete value[k];
183                     } else {
184                         value[k] = v;
185                     }
186                 }
187             }
188         }
189         return reviver.call(o,key,value);
190     };
191
192     return typeof reviver === 'function' ? walk({'':data},'') : data;
193 }
194
195 /**
196  * Replace certain Unicode characters that may be handled incorrectly by
197  * some browser implementations.
198  *
199  * @method _prepare
200  * @param s {String} parse input
201  * @return {String} sanitized JSON string ready to be validated/parsed
202  * @private
203  */
204 function _prepare(s) {
205     return s.replace(_UNICODE_EXCEPTIONS, _char);
206 }
207
208 function _isSafe(str) {
209     return l.isString(str) &&
210             !_UNSAFE.test(str.replace(_ESCAPES,'@').
211                              replace(_VALUES,']').
212                              replace(_BRACKETS,''));
213 }
214
215 function _parse(s,reviver) {
216     // sanitize
217     s = _prepare(s);
218
219     // Ensure valid JSON
220     if (_isSafe(s)) {
221         // Eval the text into a JavaScript data structure, apply the
222         // reviver function if provided, and return
223         return _revive( eval('(' + s + ')'), reviver );
224     }
225
226     // The text is not valid JSON
227     throw new SyntaxError('JSON.parse');
228 }
229
230
231
232 /* functions used by stringify */
233
234 // Utility function used to determine how to serialize a variable.
235 function _type(o) {
236     var t = typeof o;
237     return  _allowable[t] ||              // number, string, boolean, undefined
238             _allowable[_toStr.call(o)] || // Number, String, Boolean, Date
239             (t === OBJECT ?
240                 (o ? OBJECT : NULL) :     // object, array, null, misc natives
241                 UNDEFINED);               // function, unknown
242 }
243
244 // Enclose escaped strings in quotes
245 function _string(s) {
246     return QUOTE + s.replace(_SPECIAL_CHARS, _char) + QUOTE;
247 }
248
249 // Adds the provided space to the beginning of every line in the input string
250 function _indent(s,space) {
251     return s.replace(/^/gm, space);
252 }
253
254 // JavaScript implementation of stringify (see API declaration of stringify)
255 function _stringify(o,w,space) {
256     if (o === undefined) {
257         return undefined;
258     }
259
260     var replacer = isFunction(w) ? w : null,
261         format   = _toStr.call(space).match(/String|Number/) || [],
262         _date    = YAHOO.lang.JSON.dateToString,
263         stack    = [],
264         tmp,i,len;
265
266     if (replacer || !isArray(w)) {
267         w = undefined;
268     }
269
270     // Ensure whitelist keys are unique (bug 2110391)
271     if (w) {
272         tmp = {};
273         for (i = 0, len = w.length; i < len; ++i) {
274             tmp[w[i]] = true;
275         }
276         w = tmp;
277     }
278
279     // Per the spec, strings are truncated to 10 characters and numbers
280     // are converted to that number of spaces (max 10)
281     space = format[0] === 'Number' ?
282                 new Array(Math.min(Math.max(0,space),10)+1).join(" ") :
283                 (space || EMPTY).slice(0,10);
284
285     function _serialize(h,key) {
286         var value = h[key],
287             t     = _type(value),
288             a     = [],
289             colon = space ? COLON_SP : COLON,
290             arr, i, keys, k, v;
291
292         // Per the ECMA 5 spec, toJSON is applied before the replacer is
293         // called.  Also per the spec, Date.prototype.toJSON has been added, so
294         // Date instances should be serialized prior to exposure to the
295         // replacer.  I disagree with this decision, but the spec is the spec.
296         if (isObject(value) && isFunction(value.toJSON)) {
297             value = value.toJSON(key);
298         } else if (t === DATE) {
299             value = _date(value);
300         }
301
302         if (isFunction(replacer)) {
303             value = replacer.call(h,key,value);
304         }
305
306         if (value !== h[key]) {
307             t = _type(value);
308         }
309
310         switch (t) {
311             case DATE    : // intentional fallthrough.  Pre-replacer Dates are
312                            // serialized in the toJSON stage.  Dates here would
313                            // have been produced by the replacer.
314             case OBJECT  : break;
315             case STRING  : return _string(value);
316             case NUMBER  : return isFinite(value) ? value+EMPTY : NULL;
317             case BOOLEAN : return value+EMPTY;
318             case NULL    : return NULL;
319             default      : return undefined;
320         }
321
322         // Check for cyclical references in nested objects
323         for (i = stack.length - 1; i >= 0; --i) {
324             if (stack[i] === value) {
325                 throw new Error("JSON.stringify. Cyclical reference");
326             }
327         }
328
329         arr = isArray(value);
330
331         // Add the object to the processing stack
332         stack.push(value);
333
334         if (arr) { // Array
335             for (i = value.length - 1; i >= 0; --i) {
336                 a[i] = _serialize(value, i) || NULL;
337             }
338         } else {   // Object
339             // If whitelist provided, take only those keys
340             keys = w || value;
341             i = 0;
342
343             for (k in keys) {
344                 if (l.hasOwnProperty(keys, k)) {
345                     v = _serialize(value, k);
346                     if (v) {
347                         a[i++] = _string(k) + colon + v;
348                     }
349                 }
350             }
351         }
352
353         // remove the array from the stack
354         stack.pop();
355
356         if (space && a.length) {
357             return arr ?
358                 OPEN_A + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_A :
359                 OPEN_O + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_O;
360         } else {
361             return arr ?
362                 OPEN_A + a.join(COMMA) + CLOSE_A :
363                 OPEN_O + a.join(COMMA) + CLOSE_O;
364         }
365     }
366
367     // process the input
368     return _serialize({'':o},'');
369 }
370
371
372 /* Public API */
373 YAHOO.lang.JSON = {
374     /**
375      * Leverage native JSON parse if the browser has a native implementation.
376      * In general, this is a good idea.  See the Known Issues section in the
377      * JSON user guide for caveats.  The default value is true for browsers with
378      * native JSON support.
379      *
380      * @property useNativeParse
381      * @type Boolean
382      * @default true
383      * @static
384      */
385     useNativeParse : !!Native,
386
387     /**
388      * Leverage native JSON stringify if the browser has a native
389      * implementation.  In general, this is a good idea.  See the Known Issues
390      * section in the JSON user guide for caveats.  The default value is true
391      * for browsers with native JSON support.
392      *
393      * @property useNativeStringify
394      * @type Boolean
395      * @default true
396      * @static
397      */
398     useNativeStringify : !!Native,
399
400     /**
401      * Four step determination whether a string is safe to eval. In three steps,
402      * escape sequences, safe values, and properly placed open square brackets
403      * are replaced with placeholders or removed.  Then in the final step, the
404      * result of all these replacements is checked for invalid characters.
405      *
406      * @method isSafe
407      * @param str {String} JSON string to be tested
408      * @return {boolean} is the string safe for eval?
409      * @static
410      */
411     isSafe : function (s) {
412         return _isSafe(_prepare(s));
413     },
414
415     /**
416      * <p>Parse a JSON string, returning the native JavaScript
417      * representation.</p>
418      *
419      * <p>When lang.JSON.useNativeParse is true, this will defer to the native
420      * JSON.parse if the browser has a native implementation.  Otherwise, a
421      * JavaScript implementation based on http://www.json.org/json2.js
422      * is used.</p>
423      *
424      * @method parse
425      * @param s {string} JSON string data
426      * @param reviver {function} (optional) function(k,v) passed each key:value
427      *          pair of object literals, allowing pruning or altering values
428      * @return {MIXED} the native JavaScript representation of the JSON string
429      * @throws SyntaxError
430      * @static
431      */
432     parse : function (s,reviver) {
433         if (typeof s !== 'string') {
434             s += '';
435         }
436
437         return Native && YAHOO.lang.JSON.useNativeParse ?
438             Native.parse(s,reviver) : _parse(s,reviver);
439     },
440
441     /**
442      * <p>Converts an arbitrary value to a JSON string representation.</p>
443      *
444      * <p>Objects with cyclical references will trigger an exception.</p>
445      *
446      * <p>If a whitelist is provided, only matching object keys will be
447      * included.  Alternately, a replacer function may be passed as the
448      * second parameter.  This function is executed on every value in the
449      * input, and its return value will be used in place of the original value.
450      * This is useful to serialize specialized objects or class instances.</p>
451      *
452      * <p>If a positive integer or non-empty string is passed as the third
453      * parameter, the output will be formatted with carriage returns and
454      * indentation for readability.  If a String is passed (such as "\t") it
455      * will be used once for each indentation level.  If a number is passed,
456      * that number of spaces will be used.</p>
457      *
458      * <p>When lang.JSON.useNativeStringify is true, this will defer to the
459      * native JSON.stringify if the browser has a native implementation.
460      * Otherwise, a JavaScript implementation is used.</p>
461      *
462      * @method stringify
463      * @param o {MIXED} any arbitrary object to convert to JSON string
464      * @param w {Array|Function} (optional) whitelist of acceptable object keys
465      *                  to include OR a function(value,key) to alter values
466      *                  before serialization
467      * @param space {Number|String} (optional) indentation character(s) or
468      *                  depthy of spaces to format the output 
469      * @return {string} JSON string representation of the input
470      * @throws Error
471      * @static
472      */
473     stringify : function (o,w,space) {
474         return Native && YAHOO.lang.JSON.useNativeStringify ?
475             Native.stringify(o,w,space) : _stringify(o,w,space);
476     },
477
478     /**
479      * Serializes a Date instance as a UTC date string.  Used internally by
480      * the JavaScript implementation of stringify.  If you need a different
481      * Date serialization format, override this method.  If you change this,
482      * you should also set useNativeStringify to false, since native JSON
483      * implementations serialize Dates per the ECMAScript 5 spec.  You've been
484      * warned.
485      *
486      * @method dateToString
487      * @param d {Date} The Date to serialize
488      * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
489      * @static
490      */
491     dateToString : function (d) {
492         function _zeroPad(v) {
493             return v < 10 ? '0' + v : v;
494         }
495
496         return d.getUTCFullYear()         + '-' +
497             _zeroPad(d.getUTCMonth() + 1) + '-' +
498             _zeroPad(d.getUTCDate())      + 'T' +
499             _zeroPad(d.getUTCHours())     + COLON +
500             _zeroPad(d.getUTCMinutes())   + COLON +
501             _zeroPad(d.getUTCSeconds())   + 'Z';
502     },
503
504     /**
505      * Reconstitute Date instances from the default JSON UTC serialization.
506      * Reference this from a reviver function to rebuild Dates during the
507      * parse operation.
508      *
509      * @method stringToDate
510      * @param str {String} String serialization of a Date
511      * @return {Date}
512      */
513     stringToDate : function (str) {
514         var m = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);
515         if (m) {
516             var d = new Date();
517             d.setUTCFullYear(m[1], m[2]-1, m[3]);
518             d.setUTCHours(m[4], m[5], m[6], (m[7] || 0));
519             return d;
520         }
521         return str;
522     }
523 };
524
525 /**
526  * <p>Four step determination whether a string is safe to eval. In three steps,
527  * escape sequences, safe values, and properly placed open square brackets
528  * are replaced with placeholders or removed.  Then in the final step, the
529  * result of all these replacements is checked for invalid characters.</p>
530  *
531  * <p>This is an alias for isSafe.</p>
532  *
533  * @method isValid
534  * @param str {String} JSON string to be tested
535  * @return {boolean} is the string safe for eval?
536  * @static
537  * @deprecated use isSafe
538  */
539 YAHOO.lang.JSON.isValid = YAHOO.lang.JSON.isSafe;
540
541 })();
542 YAHOO.register("json", YAHOO.lang.JSON, {version: "2.9.0", build: "2800"});