]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/json/json-stringify.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / json / json-stringify.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('json-stringify', function(Y) {
9
10 /**
11  * Provides Y.JSON.stringify method for converting objects to JSON strings.
12  *
13  * @module json
14  * @submodule json-stringify
15  * @for JSON
16  * @static
17  */
18 var _JSON     = (Y.config.win || {}).JSON,
19     Lang      = Y.Lang,
20     isFunction= Lang.isFunction,
21     isObject  = Lang.isObject,
22     isArray   = Lang.isArray,
23     _toStr    = Object.prototype.toString,
24     Native    = (_toStr.call(_JSON) === '[object JSON]' && _JSON),
25     useNative = !!Native,
26     UNDEFINED = 'undefined',
27     OBJECT    = 'object',
28     NULL      = 'null',
29     STRING    = 'string',
30     NUMBER    = 'number',
31     BOOLEAN   = 'boolean',
32     DATE      = 'date',
33     _allowable= {
34         'undefined'        : UNDEFINED,
35         'string'           : STRING,
36         '[object String]'  : STRING,
37         'number'           : NUMBER,
38         '[object Number]'  : NUMBER,
39         'boolean'          : BOOLEAN,
40         '[object Boolean]' : BOOLEAN,
41         '[object Date]'    : DATE,
42         '[object RegExp]'  : OBJECT
43     },
44     EMPTY     = '',
45     OPEN_O    = '{',
46     CLOSE_O   = '}',
47     OPEN_A    = '[',
48     CLOSE_A   = ']',
49     COMMA     = ',',
50     COMMA_CR  = ",\n",
51     CR        = "\n",
52     COLON     = ':',
53     COLON_SP  = ': ',
54     QUOTE     = '"',
55
56     // Regex used to capture characters that need escaping before enclosing
57     // their containing string in quotes.
58     _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
59
60     // Character substitution map for common escapes and special characters.
61     _CHARS = {
62         '\b': '\\b',
63         '\t': '\\t',
64         '\n': '\\n',
65         '\f': '\\f',
66         '\r': '\\r',
67         '"' : '\\"',
68         '\\': '\\\\'
69     };
70
71
72 // Utility function used to determine how to serialize a variable.
73 function _type(o) {
74     var t = typeof o;
75     return  _allowable[t] ||              // number, string, boolean, undefined
76             _allowable[_toStr.call(o)] || // Number, String, Boolean, Date
77             (t === OBJECT ?
78                 (o ? OBJECT : NULL) :     // object, array, null, misc natives
79                 UNDEFINED);               // function, unknown
80 }
81
82 // Escapes a special character to a safe Unicode representation
83 function _char(c) {
84     if (!_CHARS[c]) {
85         _CHARS[c] =  '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
86     }
87     return _CHARS[c];
88 }
89
90 // Enclose escaped strings in quotes
91 function _string(s) {
92     return QUOTE + s.replace(_SPECIAL_CHARS, _char) + QUOTE;
93 }
94
95 // Adds the provided space to the beginning of every line in the input string
96 function _indent(s,space) {
97     return s.replace(/^/gm, space);
98 }
99
100 // JavaScript implementation of stringify (see API declaration of stringify)
101 function _stringify(o,w,space) {
102     if (o === undefined) {
103         return undefined;
104     }
105
106     var replacer = isFunction(w) ? w : null,
107         format   = _toStr.call(space).match(/String|Number/) || [],
108         _date    = Y.JSON.dateToString,
109         stack    = [],
110         tmp,i,len;
111
112     if (replacer || !isArray(w)) {
113         w = undefined;
114     }
115
116     // Ensure whitelist keys are unique (bug 2110391)
117     if (w) {
118         tmp = {};
119         for (i = 0, len = w.length; i < len; ++i) {
120             tmp[w[i]] = true;
121         }
122         w = tmp;
123     }
124
125     // Per the spec, strings are truncated to 10 characters and numbers
126     // are converted to that number of spaces (max 10)
127     space = format[0] === 'Number' ?
128                 new Array(Math.min(Math.max(0,space),10)+1).join(" ") :
129                 (space || EMPTY).slice(0,10);
130
131     function _serialize(h,key) {
132         var value = h[key],
133             t     = _type(value),
134             a     = [],
135             colon = space ? COLON_SP : COLON,
136             arr, i, keys, k, v;
137
138         // Per the ECMA 5 spec, toJSON is applied before the replacer is
139         // called.  Also per the spec, Date.prototype.toJSON has been added, so
140         // Date instances should be serialized prior to exposure to the
141         // replacer.  I disagree with this decision, but the spec is the spec.
142         if (isObject(value) && isFunction(value.toJSON)) {
143             value = value.toJSON(key);
144         } else if (t === DATE) {
145             value = _date(value);
146         }
147
148         if (isFunction(replacer)) {
149             value = replacer.call(h,key,value);
150         }
151
152         if (value !== h[key]) {
153             t = _type(value);
154         }
155
156         switch (t) {
157             case DATE    : // intentional fallthrough.  Pre-replacer Dates are
158                            // serialized in the toJSON stage.  Dates here would
159                            // have been produced by the replacer.
160             case OBJECT  : break;
161             case STRING  : return _string(value);
162             case NUMBER  : return isFinite(value) ? value+EMPTY : NULL;
163             case BOOLEAN : return value+EMPTY;
164             case NULL    : return NULL;
165             default      : return undefined;
166         }
167
168         // Check for cyclical references in nested objects
169         for (i = stack.length - 1; i >= 0; --i) {
170             if (stack[i] === value) {
171                 throw new Error("JSON.stringify. Cyclical reference");
172             }
173         }
174
175         arr = isArray(value);
176
177         // Add the object to the processing stack
178         stack.push(value);
179
180         if (arr) { // Array
181             for (i = value.length - 1; i >= 0; --i) {
182                 a[i] = _serialize(value, i) || NULL;
183             }
184         } else {   // Object
185             // If whitelist provided, take only those keys
186             keys = w || value;
187             i = 0;
188
189             for (k in keys) {
190                 if (keys.hasOwnProperty(k)) {
191                     v = _serialize(value, k);
192                     if (v) {
193                         a[i++] = _string(k) + colon + v;
194                     }
195                 }
196             }
197         }
198
199         // remove the array from the stack
200         stack.pop();
201
202         if (space && a.length) {
203             return arr ?
204                 OPEN_A + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_A :
205                 OPEN_O + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_O;
206         } else {
207             return arr ?
208                 OPEN_A + a.join(COMMA) + CLOSE_A :
209                 OPEN_O + a.join(COMMA) + CLOSE_O;
210         }
211     }
212
213     // process the input
214     return _serialize({'':o},'');
215 }
216
217 // Double check basic native functionality.  This is primarily to catch broken
218 // early JSON API implementations in Firefox 3.1 beta1 and beta2.
219 if ( Native ) {
220     try {
221         useNative = ( '0' === Native.stringify(0) );
222     } catch ( e ) {
223         useNative = false;
224     }
225 }
226
227 Y.mix(Y.namespace('JSON'),{
228     /**
229      * Leverage native JSON stringify if the browser has a native
230      * implementation.  In general, this is a good idea.  See the Known Issues
231      * section in the JSON user guide for caveats.  The default value is true
232      * for browsers with native JSON support.
233      *
234      * @property JSON.useNativeStringify
235      * @type Boolean
236      * @default true
237      * @static
238      */
239     useNativeStringify : useNative,
240
241     /**
242      * Serializes a Date instance as a UTC date string.  Used internally by
243      * stringify.  Override this method if you need Dates serialized in a
244      * different format.
245      *
246      * @method dateToString
247      * @param d {Date} The Date to serialize
248      * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
249      * @deprecated Use a replacer function
250      * @static
251      */
252     dateToString : function (d) {
253         function _zeroPad(v) {
254             return v < 10 ? '0' + v : v;
255         }
256
257         return d.getUTCFullYear()           + '-' +
258               _zeroPad(d.getUTCMonth() + 1) + '-' +
259               _zeroPad(d.getUTCDate())      + 'T' +
260               _zeroPad(d.getUTCHours())     + COLON +
261               _zeroPad(d.getUTCMinutes())   + COLON +
262               _zeroPad(d.getUTCSeconds())   + 'Z';
263     },
264
265     /**
266      * <p>Converts an arbitrary value to a JSON string representation.</p>
267      *
268      * <p>Objects with cyclical references will trigger an exception.</p>
269      *
270      * <p>If a whitelist is provided, only matching object keys will be
271      * included.  Alternately, a replacer function may be passed as the
272      * second parameter.  This function is executed on every value in the
273      * input, and its return value will be used in place of the original value.
274      * This is useful to serialize specialized objects or class instances.</p>
275      *
276      * <p>If a positive integer or non-empty string is passed as the third
277      * parameter, the output will be formatted with carriage returns and
278      * indentation for readability.  If a String is passed (such as "\t") it
279      * will be used once for each indentation level.  If a number is passed,
280      * that number of spaces will be used.</p>
281      *
282      * @method stringify
283      * @param o {MIXED} any arbitrary value to convert to JSON string
284      * @param w {Array|Function} (optional) whitelist of acceptable object
285      *                  keys to include, or a replacer function to modify the
286      *                  raw value before serialization
287      * @param ind {Number|String} (optional) indentation character or depth of
288      *                  spaces to format the output.
289      * @return {string} JSON string representation of the input
290      * @static
291      */
292     stringify : function (o,w,ind) {
293         return Native && Y.JSON.useNativeStringify ?
294             Native.stringify(o,w,ind) : _stringify(o,w,ind);
295     }
296 });
297
298
299 }, '3.3.0' );