Path: blob/trunk/third_party/closure/goog/json/json.js
4091 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview JSON utility functions.8*/91011goog.provide('goog.json');12goog.provide('goog.json.Replacer');13goog.provide('goog.json.Reviver');14goog.provide('goog.json.Serializer');151617/**18* @define {boolean} If true, use the native JSON parsing API.19* NOTE: The default `goog.json.parse` implementation is able to handle20* invalid JSON. JSPB used to produce invalid JSON which is not the case21* anymore so this is safe to enable for parsing JSPB. Using native JSON is22* faster and safer than the default implementation using `eval`.23*/24goog.json.USE_NATIVE_JSON = goog.define('goog.json.USE_NATIVE_JSON', false);252627/**28* Tests if a string is an invalid JSON string. This only ensures that we are29* not using any invalid characters30* @param {string} s The string to test.31* @return {boolean} True if the input is a valid JSON string.32*/33goog.json.isValid = function(s) {34'use strict';35// All empty whitespace is not valid.36if (/^\s*$/.test(s)) {37return false;38}3940// This is taken from http://www.json.org/json2.js which is released to the41// public domain.42// Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator43// inside strings. We also treat \u2028 and \u2029 as whitespace which they44// are in the RFC but IE and Safari does not match \s to these so we need to45// include them in the reg exps in all places where whitespace is allowed.46// We allowed \x7f inside strings because some tools don't escape it,47// e.g. http://www.json.org/java/org/json/JSONObject.java4849// Parsing happens in three stages. In the first stage, we run the text50// against regular expressions that look for non-JSON patterns. We are51// especially concerned with '()' and 'new' because they can cause invocation,52// and '=' because it can cause mutation. But just to be safe, we want to53// reject all unexpected forms.5455// We split the first stage into 4 regexp operations in order to work around56// crippling inefficiencies in IE's and Safari's regexp engines. First we57// replace all backslash pairs with '@' (a non-JSON character). Second, we58// replace all simple value tokens with ']' characters, but only when followed59// by a colon, comma, closing bracket or end of string. Third, we delete all60// open brackets that follow a colon or comma or that begin the text. Finally,61// we look to see that the remaining characters are only whitespace or ']' or62// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.6364// Don't make these static since they have the global flag.65const backslashesRe = /\\["\\\/bfnrtu]/g;66const simpleValuesRe =67/(?:"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)[\s\u2028\u2029]*(?=:|,|]|}|$)/g;68const openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;69const remainderRe = /^[\],:{}\s\u2028\u2029]*$/;7071return remainderRe.test(72s.replace(backslashesRe, '@')73.replace(simpleValuesRe, ']')74.replace(openBracketsRe, ''));75};7677/**78* Logs a parsing error in `JSON.parse` solvable by using `eval`.79* @private {function(string, !Error)} The first parameter is the error message,80* the second is the exception thrown by `JSON.parse`.81*/82goog.json.errorLogger_ = () => {};838485/**86* Sets an error logger to use if there's a recoverable parsing error.87* @param {function(string, !Error)} errorLogger The first parameter is the88* error message, the second is the exception thrown by `JSON.parse`.89*/90goog.json.setErrorLogger = function(errorLogger) {91'use strict';92goog.json.errorLogger_ = errorLogger;93};949596/**97* Parses a JSON string and returns the result. This throws an exception if98* the string is an invalid JSON string.99*100* Note that this is very slow on large strings. Use JSON.parse if possible.101*102* @param {*} s The JSON string to parse.103* @throws Error if s is invalid JSON.104* @return {Object} The object generated from the JSON string, or null.105* @deprecated Use JSON.parse.106*/107goog.json.parse = goog.json.USE_NATIVE_JSON ?108/** @type {function(*):Object} */ (goog.global['JSON']['parse']) :109function(s) {110'use strict';111let error;112try {113return goog.global['JSON']['parse'](s);114} catch (ex) {115error = ex;116}117const o = String(s);118if (goog.json.isValid(o)) {119120try {121const result = /** @type {?Object} */ (eval('(' + o + ')'));122if (error) {123goog.json.errorLogger_('Invalid JSON: ' + o, error);124}125return result;126} catch (ex) {127}128}129throw new Error('Invalid JSON string: ' + o);130};131132133/**134* JSON replacer, as defined in Section 15.12.3 of the ES5 spec.135* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3136*137* TODO(nicksantos): Array should also be a valid replacer.138*139* @typedef {function(this:Object, string, *): *}140*/141goog.json.Replacer;142143144/**145* JSON reviver, as defined in Section 15.12.2 of the ES5 spec.146* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3147*148* @typedef {function(this:Object, string, *): *}149*/150goog.json.Reviver;151152153/**154* Serializes an object or a value to a JSON string.155*156* @param {*} object The object to serialize.157* @param {?goog.json.Replacer=} opt_replacer A replacer function158* called for each (key, value) pair that determines how the value159* should be serialized. By defult, this just returns the value160* and allows default serialization to kick in.161* @throws Error if there are loops in the object graph.162* @return {string} A JSON string representation of the input.163*/164goog.json.serialize = goog.json.USE_NATIVE_JSON ?165/** @type {function(*, ?goog.json.Replacer=):string} */166(goog.global['JSON']['stringify']) :167function(object, opt_replacer) {168'use strict';169// NOTE(nicksantos): Currently, we never use JSON.stringify.170//171// The last time I evaluated this, JSON.stringify had subtle bugs and172// behavior differences on all browsers, and the performance win was not173// large enough to justify all the issues. This may change in the future174// as browser implementations get better.175//176// assertSerialize in json_test contains if branches for the cases177// that fail.178return new goog.json.Serializer(opt_replacer).serialize(object);179};180181182183/**184* Class that is used to serialize JSON objects to a string.185* @param {?goog.json.Replacer=} opt_replacer Replacer.186* @constructor187*/188goog.json.Serializer = function(opt_replacer) {189'use strict';190/**191* @type {goog.json.Replacer|null|undefined}192* @private193*/194this.replacer_ = opt_replacer;195};196197198/**199* Serializes an object or a value to a JSON string.200*201* @param {*} object The object to serialize.202* @throws Error if there are loops in the object graph.203* @return {string} A JSON string representation of the input.204*/205goog.json.Serializer.prototype.serialize = function(object) {206'use strict';207const sb = [];208this.serializeInternal(object, sb);209return sb.join('');210};211212213/**214* Serializes a generic value to a JSON string215* @protected216* @param {*} object The object to serialize.217* @param {Array<string>} sb Array used as a string builder.218* @throws Error if there are loops in the object graph.219*/220goog.json.Serializer.prototype.serializeInternal = function(object, sb) {221'use strict';222if (object == null) {223// undefined == null so this branch covers undefined as well as null224sb.push('null');225return;226}227228if (typeof object == 'object') {229if (Array.isArray(object)) {230this.serializeArray(object, sb);231return;232} else if (233object instanceof String || object instanceof Number ||234object instanceof Boolean) {235object = object.valueOf();236// Fall through to switch below.237} else {238this.serializeObject_(/** @type {!Object} */ (object), sb);239return;240}241}242243switch (typeof object) {244case 'string':245this.serializeString_(object, sb);246break;247case 'number':248this.serializeNumber_(object, sb);249break;250case 'boolean':251sb.push(String(object));252break;253case 'function':254sb.push('null');255break;256default:257throw new Error('Unknown type: ' + typeof object);258}259};260261262/**263* Character mappings used internally for goog.string.quote264* @private265* @type {!Object}266*/267goog.json.Serializer.charToJsonCharCache_ = {268'\"': '\\"',269'\\': '\\\\',270'/': '\\/',271'\b': '\\b',272'\f': '\\f',273'\n': '\\n',274'\r': '\\r',275'\t': '\\t',276277'\x0B': '\\u000b' // '\v' is not supported in JScript278};279280281/**282* Regular expression used to match characters that need to be replaced.283* The S60 browser has a bug where unicode characters are not matched by284* regular expressions. The condition below detects such behaviour and285* adjusts the regular expression accordingly.286* @private287* @type {!RegExp}288*/289goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?290/[\\\"\x00-\x1f\x7f-\uffff]/g :291/[\\\"\x00-\x1f\x7f-\xff]/g;292293294/**295* Serializes a string to a JSON string296* @private297* @param {string} s The string to serialize.298* @param {Array<string>} sb Array used as a string builder.299*/300goog.json.Serializer.prototype.serializeString_ = function(s, sb) {301'use strict';302// The official JSON implementation does not work with international303// characters.304sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {305'use strict';306// caching the result improves performance by a factor 2-3307let rv = goog.json.Serializer.charToJsonCharCache_[c];308if (!rv) {309rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).slice(1);310goog.json.Serializer.charToJsonCharCache_[c] = rv;311}312return rv;313}), '"');314};315316317/**318* Serializes a number to a JSON string319* @private320* @param {number} n The number to serialize.321* @param {Array<string>} sb Array used as a string builder.322*/323goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {324'use strict';325sb.push(isFinite(n) && !isNaN(n) ? String(n) : 'null');326};327328329/**330* Serializes an array to a JSON string331* @param {Array<string>} arr The array to serialize.332* @param {Array<string>} sb Array used as a string builder.333* @protected334*/335goog.json.Serializer.prototype.serializeArray = function(arr, sb) {336'use strict';337const l = arr.length;338sb.push('[');339let sep = '';340for (let i = 0; i < l; i++) {341sb.push(sep);342343const value = arr[i];344this.serializeInternal(345this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,346sb);347348sep = ',';349}350sb.push(']');351};352353354/**355* Serializes an object to a JSON string356* @private357* @param {!Object} obj The object to serialize.358* @param {Array<string>} sb Array used as a string builder.359*/360goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {361'use strict';362sb.push('{');363let sep = '';364for (const key in obj) {365if (Object.prototype.hasOwnProperty.call(obj, key)) {366const value = obj[key];367// Skip functions.368if (typeof value != 'function') {369sb.push(sep);370this.serializeString_(key, sb);371sb.push(':');372373this.serializeInternal(374this.replacer_ ? this.replacer_.call(obj, key, value) : value, sb);375376sep = ',';377}378}379}380sb.push('}');381};382383384