Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/json/json.js
4091 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview JSON utility functions.
9
*/
10
11
12
goog.provide('goog.json');
13
goog.provide('goog.json.Replacer');
14
goog.provide('goog.json.Reviver');
15
goog.provide('goog.json.Serializer');
16
17
18
/**
19
* @define {boolean} If true, use the native JSON parsing API.
20
* NOTE: The default `goog.json.parse` implementation is able to handle
21
* invalid JSON. JSPB used to produce invalid JSON which is not the case
22
* anymore so this is safe to enable for parsing JSPB. Using native JSON is
23
* faster and safer than the default implementation using `eval`.
24
*/
25
goog.json.USE_NATIVE_JSON = goog.define('goog.json.USE_NATIVE_JSON', false);
26
27
28
/**
29
* Tests if a string is an invalid JSON string. This only ensures that we are
30
* not using any invalid characters
31
* @param {string} s The string to test.
32
* @return {boolean} True if the input is a valid JSON string.
33
*/
34
goog.json.isValid = function(s) {
35
'use strict';
36
// All empty whitespace is not valid.
37
if (/^\s*$/.test(s)) {
38
return false;
39
}
40
41
// This is taken from http://www.json.org/json2.js which is released to the
42
// public domain.
43
// Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
44
// inside strings. We also treat \u2028 and \u2029 as whitespace which they
45
// are in the RFC but IE and Safari does not match \s to these so we need to
46
// include them in the reg exps in all places where whitespace is allowed.
47
// We allowed \x7f inside strings because some tools don't escape it,
48
// e.g. http://www.json.org/java/org/json/JSONObject.java
49
50
// Parsing happens in three stages. In the first stage, we run the text
51
// against regular expressions that look for non-JSON patterns. We are
52
// especially concerned with '()' and 'new' because they can cause invocation,
53
// and '=' because it can cause mutation. But just to be safe, we want to
54
// reject all unexpected forms.
55
56
// We split the first stage into 4 regexp operations in order to work around
57
// crippling inefficiencies in IE's and Safari's regexp engines. First we
58
// replace all backslash pairs with '@' (a non-JSON character). Second, we
59
// replace all simple value tokens with ']' characters, but only when followed
60
// by a colon, comma, closing bracket or end of string. Third, we delete all
61
// open brackets that follow a colon or comma or that begin the text. Finally,
62
// we look to see that the remaining characters are only whitespace or ']' or
63
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
64
65
// Don't make these static since they have the global flag.
66
const backslashesRe = /\\["\\\/bfnrtu]/g;
67
const simpleValuesRe =
68
/(?:"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)[\s\u2028\u2029]*(?=:|,|]|}|$)/g;
69
const openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
70
const remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
71
72
return remainderRe.test(
73
s.replace(backslashesRe, '@')
74
.replace(simpleValuesRe, ']')
75
.replace(openBracketsRe, ''));
76
};
77
78
/**
79
* Logs a parsing error in `JSON.parse` solvable by using `eval`.
80
* @private {function(string, !Error)} The first parameter is the error message,
81
* the second is the exception thrown by `JSON.parse`.
82
*/
83
goog.json.errorLogger_ = () => {};
84
85
86
/**
87
* Sets an error logger to use if there's a recoverable parsing error.
88
* @param {function(string, !Error)} errorLogger The first parameter is the
89
* error message, the second is the exception thrown by `JSON.parse`.
90
*/
91
goog.json.setErrorLogger = function(errorLogger) {
92
'use strict';
93
goog.json.errorLogger_ = errorLogger;
94
};
95
96
97
/**
98
* Parses a JSON string and returns the result. This throws an exception if
99
* the string is an invalid JSON string.
100
*
101
* Note that this is very slow on large strings. Use JSON.parse if possible.
102
*
103
* @param {*} s The JSON string to parse.
104
* @throws Error if s is invalid JSON.
105
* @return {Object} The object generated from the JSON string, or null.
106
* @deprecated Use JSON.parse.
107
*/
108
goog.json.parse = goog.json.USE_NATIVE_JSON ?
109
/** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
110
function(s) {
111
'use strict';
112
let error;
113
try {
114
return goog.global['JSON']['parse'](s);
115
} catch (ex) {
116
error = ex;
117
}
118
const o = String(s);
119
if (goog.json.isValid(o)) {
120
121
try {
122
const result = /** @type {?Object} */ (eval('(' + o + ')'));
123
if (error) {
124
goog.json.errorLogger_('Invalid JSON: ' + o, error);
125
}
126
return result;
127
} catch (ex) {
128
}
129
}
130
throw new Error('Invalid JSON string: ' + o);
131
};
132
133
134
/**
135
* JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
136
* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
137
*
138
* TODO(nicksantos): Array should also be a valid replacer.
139
*
140
* @typedef {function(this:Object, string, *): *}
141
*/
142
goog.json.Replacer;
143
144
145
/**
146
* JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
147
* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
148
*
149
* @typedef {function(this:Object, string, *): *}
150
*/
151
goog.json.Reviver;
152
153
154
/**
155
* Serializes an object or a value to a JSON string.
156
*
157
* @param {*} object The object to serialize.
158
* @param {?goog.json.Replacer=} opt_replacer A replacer function
159
* called for each (key, value) pair that determines how the value
160
* should be serialized. By defult, this just returns the value
161
* and allows default serialization to kick in.
162
* @throws Error if there are loops in the object graph.
163
* @return {string} A JSON string representation of the input.
164
*/
165
goog.json.serialize = goog.json.USE_NATIVE_JSON ?
166
/** @type {function(*, ?goog.json.Replacer=):string} */
167
(goog.global['JSON']['stringify']) :
168
function(object, opt_replacer) {
169
'use strict';
170
// NOTE(nicksantos): Currently, we never use JSON.stringify.
171
//
172
// The last time I evaluated this, JSON.stringify had subtle bugs and
173
// behavior differences on all browsers, and the performance win was not
174
// large enough to justify all the issues. This may change in the future
175
// as browser implementations get better.
176
//
177
// assertSerialize in json_test contains if branches for the cases
178
// that fail.
179
return new goog.json.Serializer(opt_replacer).serialize(object);
180
};
181
182
183
184
/**
185
* Class that is used to serialize JSON objects to a string.
186
* @param {?goog.json.Replacer=} opt_replacer Replacer.
187
* @constructor
188
*/
189
goog.json.Serializer = function(opt_replacer) {
190
'use strict';
191
/**
192
* @type {goog.json.Replacer|null|undefined}
193
* @private
194
*/
195
this.replacer_ = opt_replacer;
196
};
197
198
199
/**
200
* Serializes an object or a value to a JSON string.
201
*
202
* @param {*} object The object to serialize.
203
* @throws Error if there are loops in the object graph.
204
* @return {string} A JSON string representation of the input.
205
*/
206
goog.json.Serializer.prototype.serialize = function(object) {
207
'use strict';
208
const sb = [];
209
this.serializeInternal(object, sb);
210
return sb.join('');
211
};
212
213
214
/**
215
* Serializes a generic value to a JSON string
216
* @protected
217
* @param {*} object The object to serialize.
218
* @param {Array<string>} sb Array used as a string builder.
219
* @throws Error if there are loops in the object graph.
220
*/
221
goog.json.Serializer.prototype.serializeInternal = function(object, sb) {
222
'use strict';
223
if (object == null) {
224
// undefined == null so this branch covers undefined as well as null
225
sb.push('null');
226
return;
227
}
228
229
if (typeof object == 'object') {
230
if (Array.isArray(object)) {
231
this.serializeArray(object, sb);
232
return;
233
} else if (
234
object instanceof String || object instanceof Number ||
235
object instanceof Boolean) {
236
object = object.valueOf();
237
// Fall through to switch below.
238
} else {
239
this.serializeObject_(/** @type {!Object} */ (object), sb);
240
return;
241
}
242
}
243
244
switch (typeof object) {
245
case 'string':
246
this.serializeString_(object, sb);
247
break;
248
case 'number':
249
this.serializeNumber_(object, sb);
250
break;
251
case 'boolean':
252
sb.push(String(object));
253
break;
254
case 'function':
255
sb.push('null');
256
break;
257
default:
258
throw new Error('Unknown type: ' + typeof object);
259
}
260
};
261
262
263
/**
264
* Character mappings used internally for goog.string.quote
265
* @private
266
* @type {!Object}
267
*/
268
goog.json.Serializer.charToJsonCharCache_ = {
269
'\"': '\\"',
270
'\\': '\\\\',
271
'/': '\\/',
272
'\b': '\\b',
273
'\f': '\\f',
274
'\n': '\\n',
275
'\r': '\\r',
276
'\t': '\\t',
277
278
'\x0B': '\\u000b' // '\v' is not supported in JScript
279
};
280
281
282
/**
283
* Regular expression used to match characters that need to be replaced.
284
* The S60 browser has a bug where unicode characters are not matched by
285
* regular expressions. The condition below detects such behaviour and
286
* adjusts the regular expression accordingly.
287
* @private
288
* @type {!RegExp}
289
*/
290
goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
291
/[\\\"\x00-\x1f\x7f-\uffff]/g :
292
/[\\\"\x00-\x1f\x7f-\xff]/g;
293
294
295
/**
296
* Serializes a string to a JSON string
297
* @private
298
* @param {string} s The string to serialize.
299
* @param {Array<string>} sb Array used as a string builder.
300
*/
301
goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
302
'use strict';
303
// The official JSON implementation does not work with international
304
// characters.
305
sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
306
'use strict';
307
// caching the result improves performance by a factor 2-3
308
let rv = goog.json.Serializer.charToJsonCharCache_[c];
309
if (!rv) {
310
rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).slice(1);
311
goog.json.Serializer.charToJsonCharCache_[c] = rv;
312
}
313
return rv;
314
}), '"');
315
};
316
317
318
/**
319
* Serializes a number to a JSON string
320
* @private
321
* @param {number} n The number to serialize.
322
* @param {Array<string>} sb Array used as a string builder.
323
*/
324
goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
325
'use strict';
326
sb.push(isFinite(n) && !isNaN(n) ? String(n) : 'null');
327
};
328
329
330
/**
331
* Serializes an array to a JSON string
332
* @param {Array<string>} arr The array to serialize.
333
* @param {Array<string>} sb Array used as a string builder.
334
* @protected
335
*/
336
goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
337
'use strict';
338
const l = arr.length;
339
sb.push('[');
340
let sep = '';
341
for (let i = 0; i < l; i++) {
342
sb.push(sep);
343
344
const value = arr[i];
345
this.serializeInternal(
346
this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
347
sb);
348
349
sep = ',';
350
}
351
sb.push(']');
352
};
353
354
355
/**
356
* Serializes an object to a JSON string
357
* @private
358
* @param {!Object} obj The object to serialize.
359
* @param {Array<string>} sb Array used as a string builder.
360
*/
361
goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
362
'use strict';
363
sb.push('{');
364
let sep = '';
365
for (const key in obj) {
366
if (Object.prototype.hasOwnProperty.call(obj, key)) {
367
const value = obj[key];
368
// Skip functions.
369
if (typeof value != 'function') {
370
sb.push(sep);
371
this.serializeString_(key, sb);
372
sb.push(':');
373
374
this.serializeInternal(
375
this.replacer_ ? this.replacer_.call(obj, key, value) : value, sb);
376
377
sep = ',';
378
}
379
}
380
}
381
sb.push('}');
382
};
383
384