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