OLD | NEW |
| (Empty) |
1 // Copyright 2006 Google Inc. | |
2 // All Rights Reserved. | |
3 // | |
4 // Redistribution and use in source and binary forms, with or without | |
5 // modification, are permitted provided that the following conditions | |
6 // are met: | |
7 // | |
8 // * Redistributions of source code must retain the above copyright | |
9 // notice, this list of conditions and the following disclaimer. | |
10 // * Redistributions in binary form must reproduce the above copyright | |
11 // notice, this list of conditions and the following disclaimer in | |
12 // the documentation and/or other materials provided with the | |
13 // distribution. | |
14 // | |
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
16 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
17 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
18 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
19 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
20 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
21 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
22 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
23 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
24 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
25 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
26 // POSSIBILITY OF SUCH DAMAGE. | |
27 | |
28 // NOTE: This file has been changed from the one on doctype. The following | |
29 // changes were made: | |
30 // - Modified unsafeParse() to use new Function() instead of eval() because eval | |
31 // is not allowed inside v8 extensions. | |
32 // - Modified parse() to delegate to unsafeParse() instead of calling eval() | |
33 // directly. | |
34 | |
35 /** | |
36 * @fileoverview JSON utility functions | |
37 */ | |
38 | |
39 | |
40 | |
41 goog.provide('goog.json'); | |
42 goog.provide('goog.json.Serializer'); | |
43 | |
44 | |
45 /** | |
46 * Tests if a string is an invalid JSON string. This only ensures that we are | |
47 * not using any invalid characters | |
48 * @param {string} s The string to test. | |
49 * @return {boolean} True if the input is a valid JSON string. | |
50 * @private | |
51 */ | |
52 goog.json.isValid_ = function(s) { | |
53 // All empty whitespace is not valid. | |
54 if (/^\s*$/.test(s)) { | |
55 return false; | |
56 } | |
57 | |
58 // This is taken from http://www.json.org/json2.js which is released to the | |
59 // public domain. | |
60 // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator | |
61 // inside strings. We also treat \u2028 and \u2029 as whitespace which they | |
62 // are in the RFC but IE and Safari does not match \s to these so we need to | |
63 // include them in the reg exps in all places where whitespace is allowed. | |
64 | |
65 // Parsing happens in three stages. In the first stage, we run the text | |
66 // against regular expressions that look for non-JSON patterns. We are | |
67 // especially concerned with '()' and 'new' because they can cause invocation, | |
68 // and '=' because it can cause mutation. But just to be safe, we want to | |
69 // reject all unexpected forms. | |
70 | |
71 // We split the first stage into 4 regexp operations in order to work around | |
72 // crippling inefficiencies in IE's and Safari's regexp engines. First we | |
73 // replace all backslash pairs with '@' (a non-JSON character). Second, we | |
74 // replace all simple value tokens with ']' characters. Third, we delete all | |
75 // open brackets that follow a colon or comma or that begin the text. Finally, | |
76 // we look to see that the remaining characters are only whitespace or ']' or | |
77 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. | |
78 | |
79 // Don't make these static since they have the global flag. | |
80 var backslashesRe = /\\["\\\/bfnrtu]/g; | |
81 var simpleValuesRe = | |
82 /"[^"\\\n\r\u2028\u2029\x00-\x1f\x7f-\x9f]*"|true|false|null|-?\d+(?:\.\d*
)?(?:[eE][+\-]?\d+)?/g; | |
83 var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g; | |
84 var remainderRe = /^[\],:{}\s\u2028\u2029]*$/; | |
85 | |
86 return remainderRe.test(s.replace(backslashesRe, '@'). | |
87 replace(simpleValuesRe, ']'). | |
88 replace(openBracketsRe, '')); | |
89 }; | |
90 | |
91 | |
92 /** | |
93 * Parses a JSON string and returns the result. This throws an exception if | |
94 * the string is an invalid JSON string. | |
95 * | |
96 * If the user agent has built in support for parsing JSON (using | |
97 * <code>String.prototype.parseJSON</code>) that will be used. | |
98 * | |
99 * Note that this is very slow on large strings. If you trust the source of | |
100 * the string then you should use unsafeParse instead. | |
101 * | |
102 * @param {string} s The JSON string to parse. | |
103 * @return {Object} The object generated from the JSON string. | |
104 */ | |
105 goog.json.parse = function(s) { | |
106 s = String(s); | |
107 if (typeof s.parseJSON == 'function') { | |
108 return s.parseJSON(); | |
109 } | |
110 if (goog.json.isValid_(s)) { | |
111 /** @preserveTry */ | |
112 try { | |
113 return goog.json.unsafeParse(s); | |
114 } catch (ex) { | |
115 } | |
116 } | |
117 throw Error('Invalid JSON string: ' + s); | |
118 }; | |
119 | |
120 | |
121 /** | |
122 * Parses a JSON string and returns the result. This uses eval so it is open | |
123 * to security issues and it should only be used if you trust the source. | |
124 * | |
125 * @param {string} s The JSON string to parse. | |
126 * @return {Object} The object generated from the JSON string. | |
127 */ | |
128 goog.json.unsafeParse = function(s) { | |
129 // This is lame. V8 disallows direct access to eval() in extensions (see: | |
130 // v8::internal::Parser::ParseLeftHandSideExpression()). So we must use this | |
131 // nasty hack instead. | |
132 return new Function('return ('+ s + ')')(); | |
133 }; | |
134 | |
135 | |
136 /** | |
137 * Instance of the serializer object. | |
138 * @type {goog.json.Serializer} | |
139 * @private | |
140 */ | |
141 goog.json.serializer_ = null; | |
142 | |
143 | |
144 /** | |
145 * Serializes an object or a value to a JSON string. | |
146 * | |
147 * If the user agent has built in support for serializing JSON (using | |
148 * <code>Object.prototype.toJSONString</code>) that will be used. | |
149 * | |
150 * @param {Object} object The object to serialize. | |
151 * @throws Error if there are loops in the object graph. | |
152 * @return {string} A JSON string representation of the input. | |
153 */ | |
154 goog.json.serialize = function(object) { | |
155 if (!goog.json.serializer_) { | |
156 goog.json.serializer_ = new goog.json.Serializer; | |
157 } | |
158 return goog.json.serializer_.serialize(object); | |
159 }; | |
160 | |
161 | |
162 | |
163 /** | |
164 * Class that is used to serialize JSON objects to a string. | |
165 * @constructor | |
166 */ | |
167 goog.json.Serializer = function() { | |
168 }; | |
169 | |
170 | |
171 /** | |
172 * Serializes an object or a value to a JSON string. | |
173 * | |
174 * If the user agent has built in support for serializing JSON (using | |
175 * <code>Object.prototype.toJSONString</code>) that will be used. | |
176 * | |
177 * @param {Object?} object The object to serialize. | |
178 * @throws Error if there are loops in the object graph. | |
179 * @return {string} A JSON string representation of the input. | |
180 */ | |
181 goog.json.Serializer.prototype.serialize = function(object) { | |
182 // null and undefined cannot have properties. (null == undefined) | |
183 if (object != null && typeof object.toJSONString == 'function') { | |
184 return object.toJSONString(); | |
185 } | |
186 var sb = []; | |
187 this.serialize_(object, sb); | |
188 return sb.join(''); | |
189 }; | |
190 | |
191 | |
192 /** | |
193 * Serializes a generic value to a JSON string | |
194 * @private | |
195 * @param {Object?} object The object to serialize. | |
196 * @param {Array} sb Array used as a string builder. | |
197 * @throws Error if there are loops in the object graph. | |
198 */ | |
199 goog.json.Serializer.prototype.serialize_ = function(object, sb) { | |
200 switch (typeof object) { | |
201 case 'string': | |
202 this.serializeString_(object, sb); | |
203 break; | |
204 case 'number': | |
205 this.serializeNumber_(object, sb); | |
206 break; | |
207 case 'boolean': | |
208 sb.push(object); | |
209 break; | |
210 case 'undefined': | |
211 sb.push('null'); | |
212 break; | |
213 case 'object': | |
214 if (object == null) { | |
215 sb.push('null'); | |
216 break; | |
217 } | |
218 if (goog.isArray(object)) { | |
219 this.serializeArray_(object, sb); | |
220 break; | |
221 } | |
222 // should we allow new String, new Number and new Boolean to be treated | |
223 // as string, number and boolean? Most implementations do not and the | |
224 // need is not very big | |
225 this.serializeObject_(object, sb); | |
226 break; | |
227 default: | |
228 throw Error('Unknown type: ' + typeof object); | |
229 } | |
230 }; | |
231 | |
232 | |
233 /** | |
234 * Character mappings used internally for goog.string.quote | |
235 * @private | |
236 * @type {Object} | |
237 */ | |
238 goog.json.Serializer.charToJsonCharCache_ = { | |
239 '\"': '\\"', | |
240 '\\': '\\\\', | |
241 '/': '\\/', | |
242 '\b': '\\b', | |
243 '\f': '\\f', | |
244 '\n': '\\n', | |
245 '\r': '\\r', | |
246 '\t': '\\t', | |
247 | |
248 '\x0B': '\\u000b' // '\v' is not supported in JScript | |
249 }; | |
250 | |
251 | |
252 /** | |
253 * Serializes a string to a JSON string | |
254 * @private | |
255 * @param {string} s The string to serialize. | |
256 * @param {Array} sb Array used as a string builder. | |
257 */ | |
258 goog.json.Serializer.prototype.serializeString_ = function(s, sb) { | |
259 // The official JSON implementation does not work with international | |
260 // characters. | |
261 sb.push('"', s.replace(/[\\\"\x00-\x1f\x80-\uffff]/g, function(c) { | |
262 // caching the result improves performance by a factor 2-3 | |
263 if (c in goog.json.Serializer.charToJsonCharCache_) { | |
264 return goog.json.Serializer.charToJsonCharCache_[c]; | |
265 } | |
266 | |
267 var cc = c.charCodeAt(0); | |
268 var rv = '\\u'; | |
269 if (cc < 16) { | |
270 rv += '000'; | |
271 } else if (cc < 256) { | |
272 rv += '00'; | |
273 } else if (cc < 4096) { // \u1000 | |
274 rv += '0'; | |
275 } | |
276 return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16); | |
277 }), '"'); | |
278 }; | |
279 | |
280 | |
281 /** | |
282 * Serializes a number to a JSON string | |
283 * @private | |
284 * @param {number} n The number to serialize. | |
285 * @param {Array} sb Array used as a string builder. | |
286 */ | |
287 goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) { | |
288 sb.push(isFinite(n) && !isNaN(n) ? n : 'null'); | |
289 }; | |
290 | |
291 | |
292 /** | |
293 * Serializes an array to a JSON string | |
294 * @private | |
295 * @param {Array} arr The array to serialize. | |
296 * @param {Array} sb Array used as a string builder. | |
297 */ | |
298 goog.json.Serializer.prototype.serializeArray_ = function(arr, sb) { | |
299 var l = arr.length; | |
300 sb.push('['); | |
301 var sep = ''; | |
302 for (var i = 0; i < l; i++) { | |
303 sb.push(sep) | |
304 this.serialize_(arr[i], sb); | |
305 sep = ','; | |
306 } | |
307 sb.push(']'); | |
308 }; | |
309 | |
310 | |
311 /** | |
312 * Serializes an object to a JSON string | |
313 * @private | |
314 * @param {Object} obj The object to serialize. | |
315 * @param {Array} sb Array used as a string builder. | |
316 */ | |
317 goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) { | |
318 sb.push('{'); | |
319 var sep = ''; | |
320 for (var key in obj) { | |
321 sb.push(sep); | |
322 this.serializeString_(key, sb); | |
323 sb.push(':'); | |
324 this.serialize_(obj[key], sb); | |
325 sep = ','; | |
326 } | |
327 sb.push('}'); | |
328 }; | |
OLD | NEW |