MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / json-stringify / json-stringify.js
blobbe3324df12e2887038a984dcc0700967776cd7b9
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('json-stringify', function(Y) {
9 /**
10  * Provides Y.JSON.stringify method for converting objects to JSON strings.
11  *
12  * @module json
13  * @submodule json-stringify
14  * @for JSON
15  * @static
16  */
17 var _JSON     = (Y.config.win || {}).JSON,
18     Lang      = Y.Lang,
19     isFunction= Lang.isFunction,
20     isObject  = Lang.isObject,
21     isArray   = Lang.isArray,
22     _toStr    = Object.prototype.toString,
23     Native    = (_toStr.call(_JSON) === '[object JSON]' && _JSON),
24     useNative = !!Native,
25     UNDEFINED = 'undefined',
26     OBJECT    = 'object',
27     NULL      = 'null',
28     STRING    = 'string',
29     NUMBER    = 'number',
30     BOOLEAN   = 'boolean',
31     DATE      = 'date',
32     _allowable= {
33         'undefined'        : UNDEFINED,
34         'string'           : STRING,
35         '[object String]'  : STRING,
36         'number'           : NUMBER,
37         '[object Number]'  : NUMBER,
38         'boolean'          : BOOLEAN,
39         '[object Boolean]' : BOOLEAN,
40         '[object Date]'    : DATE,
41         '[object RegExp]'  : OBJECT
42     },
43     EMPTY     = '',
44     OPEN_O    = '{',
45     CLOSE_O   = '}',
46     OPEN_A    = '[',
47     CLOSE_A   = ']',
48     COMMA     = ',',
49     COMMA_CR  = ",\n",
50     CR        = "\n",
51     COLON     = ':',
52     COLON_SP  = ': ',
53     QUOTE     = '"',
55     // Regex used to capture characters that need escaping before enclosing
56     // their containing string in quotes.
57     _SPECIAL = /[\x00-\x07\x0b\x0e-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
59     // Character substitution map for common escapes and special characters.
60     _COMMON = [
61         [/\\/g, '\\\\'],
62         [/\"/g, '\\"'],
63         [/\x08/g, '\\b'],
64         [/\x09/g, '\\t'],
65         [/\x0a/g, '\\n'],
66         [/\x0c/g, '\\f'],
67         [/\x0d/g, '\\r']
68     ],
69     _COMMON_LENGTH = _COMMON.length,
71     // In-process optimization for special character escapes that haven't yet
72     // been promoted to _COMMON
73     _CHAR = {},
75     // Per-char counter to determine if it's worth fast tracking a special
76     // character escape sequence.
77     _CHAR_COUNT, _CACHE_THRESHOLD;
79 // Utility function used to determine how to serialize a variable.
80 function _type(o) {
81     var t = typeof o;
82     return  _allowable[t] ||              // number, string, boolean, undefined
83             _allowable[_toStr.call(o)] || // Number, String, Boolean, Date
84             (t === OBJECT ?
85                 (o ? OBJECT : NULL) :     // object, array, null, misc natives
86                 UNDEFINED);               // function, unknown
89 // Escapes a special character to a safe Unicode representation
90 function _char(c) {
91     if (!_CHAR[c]) {
92         _CHAR[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
93         _CHAR_COUNT[c] = 0;
94     }
96     // === to avoid this conditional for the remainder of the current operation
97     if (++_CHAR_COUNT[c] === _CACHE_THRESHOLD) {
98         _COMMON.push([new RegExp(c, 'g'), _CHAR[c]]);
99         _COMMON_LENGTH = _COMMON.length;
100     }
102     return _CHAR[c];
105 // Enclose escaped strings in quotes
106 function _string(s) {
107     var i, chr;
109     // Preprocess the string against common characters to avoid function
110     // overhead associated with replacement via function.
111     for (i = 0; i < _COMMON_LENGTH; i++) {
112         chr = _COMMON[i];
113         s = s.replace(chr[0], chr[1]);
114     }
115     
116     // original function replace for the not-as-common set of chars
117     return QUOTE + s.replace(_SPECIAL, _char) + QUOTE;
120 // Adds the provided space to the beginning of every line in the input string
121 function _indent(s,space) {
122     return s.replace(/^/gm, space);
125 // JavaScript implementation of stringify (see API declaration of stringify)
126 function _stringify(o,w,space) {
127     if (o === undefined) {
128         return undefined;
129     }
131     var replacer = isFunction(w) ? w : null,
132         format   = _toStr.call(space).match(/String|Number/) || [],
133         _date    = Y.JSON.dateToString,
134         stack    = [],
135         tmp,i,len;
137     _CHAR_COUNT      = {};
138     _CACHE_THRESHOLD = Y.JSON.charCacheThreshold;
140     if (replacer || !isArray(w)) {
141         w = undefined;
142     }
144     // Ensure whitelist keys are unique (bug 2110391)
145     if (w) {
146         tmp = {};
147         for (i = 0, len = w.length; i < len; ++i) {
148             tmp[w[i]] = true;
149         }
150         w = tmp;
151     }
153     // Per the spec, strings are truncated to 10 characters and numbers
154     // are converted to that number of spaces (max 10)
155     space = format[0] === 'Number' ?
156                 new Array(Math.min(Math.max(0,space),10)+1).join(" ") :
157                 (space || EMPTY).slice(0,10);
159     function _serialize(h,key) {
160         var value = h[key],
161             t     = _type(value),
162             a     = [],
163             colon = space ? COLON_SP : COLON,
164             arr, i, keys, k, v;
166         // Per the ECMA 5 spec, toJSON is applied before the replacer is
167         // called.  Also per the spec, Date.prototype.toJSON has been added, so
168         // Date instances should be serialized prior to exposure to the
169         // replacer.  I disagree with this decision, but the spec is the spec.
170         if (isObject(value) && isFunction(value.toJSON)) {
171             value = value.toJSON(key);
172         } else if (t === DATE) {
173             value = _date(value);
174         }
176         if (isFunction(replacer)) {
177             value = replacer.call(h,key,value);
178         }
180         if (value !== h[key]) {
181             t = _type(value);
182         }
184         switch (t) {
185             case DATE    : // intentional fallthrough.  Pre-replacer Dates are
186                            // serialized in the toJSON stage.  Dates here would
187                            // have been produced by the replacer.
188             case OBJECT  : break;
189             case STRING  : return _string(value);
190             case NUMBER  : return isFinite(value) ? value+EMPTY : NULL;
191             case BOOLEAN : return value+EMPTY;
192             case NULL    : return NULL;
193             default      : return undefined;
194         }
196         // Check for cyclical references in nested objects
197         for (i = stack.length - 1; i >= 0; --i) {
198             if (stack[i] === value) {
199                 throw new Error("JSON.stringify. Cyclical reference");
200             }
201         }
203         arr = isArray(value);
205         // Add the object to the processing stack
206         stack.push(value);
208         if (arr) { // Array
209             for (i = value.length - 1; i >= 0; --i) {
210                 a[i] = _serialize(value, i) || NULL;
211             }
212         } else {   // Object
213             // If whitelist provided, take only those keys
214             keys = w || value;
215             i = 0;
217             for (k in keys) {
218                 if (keys.hasOwnProperty(k)) {
219                     v = _serialize(value, k);
220                     if (v) {
221                         a[i++] = _string(k) + colon + v;
222                     }
223                 }
224             }
225         }
227         // remove the array from the stack
228         stack.pop();
230         if (space && a.length) {
231             return arr ?
232                 OPEN_A + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_A :
233                 OPEN_O + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_O;
234         } else {
235             return arr ?
236                 OPEN_A + a.join(COMMA) + CLOSE_A :
237                 OPEN_O + a.join(COMMA) + CLOSE_O;
238         }
239     }
241     // process the input
242     return _serialize({'':o},'');
245 // Double check basic native functionality.  This is primarily to catch broken
246 // early JSON API implementations in Firefox 3.1 beta1 and beta2.
247 if ( Native ) {
248     try {
249         useNative = ( '0' === Native.stringify(0) );
250     } catch ( e ) {
251         useNative = false;
252     }
255 Y.mix(Y.namespace('JSON'),{
256     /**
257      * Leverage native JSON stringify if the browser has a native
258      * implementation.  In general, this is a good idea.  See the Known Issues
259      * section in the JSON user guide for caveats.  The default value is true
260      * for browsers with native JSON support.
261      *
262      * @property useNativeStringify
263      * @type Boolean
264      * @default true
265      * @static
266      */
267     useNativeStringify : useNative,
269     /**
270      * Serializes a Date instance as a UTC date string.  Used internally by
271      * stringify.  Override this method if you need Dates serialized in a
272      * different format.
273      *
274      * @method dateToString
275      * @param d {Date} The Date to serialize
276      * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
277      * @deprecated Use a replacer function
278      * @static
279      */
280     dateToString : function (d) {
281         function _zeroPad(v) {
282             return v < 10 ? '0' + v : v;
283         }
285         return d.getUTCFullYear()           + '-' +
286               _zeroPad(d.getUTCMonth() + 1) + '-' +
287               _zeroPad(d.getUTCDate())      + 'T' +
288               _zeroPad(d.getUTCHours())     + COLON +
289               _zeroPad(d.getUTCMinutes())   + COLON +
290               _zeroPad(d.getUTCSeconds())   + 'Z';
291     },
293     /**
294      * <p>Converts an arbitrary value to a JSON string representation.</p>
295      *
296      * <p>Objects with cyclical references will trigger an exception.</p>
297      *
298      * <p>If a whitelist is provided, only matching object keys will be
299      * included.  Alternately, a replacer function may be passed as the
300      * second parameter.  This function is executed on every value in the
301      * input, and its return value will be used in place of the original value.
302      * This is useful to serialize specialized objects or class instances.</p>
303      *
304      * <p>If a positive integer or non-empty string is passed as the third
305      * parameter, the output will be formatted with carriage returns and
306      * indentation for readability.  If a String is passed (such as "\t") it
307      * will be used once for each indentation level.  If a number is passed,
308      * that number of spaces will be used.</p>
309      *
310      * @method stringify
311      * @param o {MIXED} any arbitrary value to convert to JSON string
312      * @param w {Array|Function} (optional) whitelist of acceptable object
313      *                  keys to include, or a replacer function to modify the
314      *                  raw value before serialization
315      * @param ind {Number|String} (optional) indentation character or depth of
316      *                  spaces to format the output.
317      * @return {string} JSON string representation of the input
318      * @static
319      */
320     stringify : function (o,w,ind) {
321         return Native && Y.JSON.useNativeStringify ?
322             Native.stringify(o,w,ind) : _stringify(o,w,ind);
323     },
325     /**
326      * <p>Number of occurrences of a special character within a single call to
327      * stringify that should trigger promotion of that character to a dedicated
328      * preprocess step for future calls.  This is only used in environments
329      * that don't support native JSON, or when useNativeStringify is set to
330      * false.</p>
331      *
332      * <p>So, if set to 50 and an object is passed to stringify that includes
333      * strings containing the special character \x07 more than 50 times,
334      * subsequent calls to stringify will process object strings through a
335      * faster serialization path for \x07 before using the generic, slower,
336      * replacement process for all special characters.</p>
337      *
338      * <p>To prime the preprocessor cache, set this value to 1, then call
339      * <code>Y.JSON.stringify("<em>(all special characters to
340      * cache)</em>");</code>, then return this setting to a more conservative
341      * value.</p>
342      *
343      * <p>Special characters \ " \b \t \n \f \r are already cached.</p>
344      *
345      * @property charCacheThreshold
346      * @static
347      * @default 100
348      * @type {Number}
349      */
350     charCacheThreshold: 100
354 }, '3.5.1' ,{requires:['yui-base']});