Path: blob/trunk/third_party/closure/goog/json/json.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview JSON utility functions.16* @author [email protected] (Erik Arvidsson)17*/181920goog.provide('goog.json');21goog.provide('goog.json.Replacer');22goog.provide('goog.json.Reviver');23goog.provide('goog.json.Serializer');242526/**27* @define {boolean} If true, use the native JSON parsing API.28* NOTE: The default {@code goog.json.parse} implementation is able to handle29* invalid JSON. JSPB used to produce invalid JSON which is not the case30* anymore so this is safe to enable for parsing JSPB. Using native JSON is31* faster and safer than the default implementation using {@code eval}.32*/33goog.define('goog.json.USE_NATIVE_JSON', false);3435/**36* @define {boolean} If true, try the native JSON parsing API first. If it37* fails, log an error and use {@code eval} instead. This is useful when38* transitioning to {@code goog.json.USE_NATIVE_JSON}. The error logger needs to39* be set by {@code goog.json.setErrorLogger}. If it is not set then the error40* is ignored.41*/42goog.define('goog.json.TRY_NATIVE_JSON', false);434445/**46* Tests if a string is an invalid JSON string. This only ensures that we are47* not using any invalid characters48* @param {string} s The string to test.49* @return {boolean} True if the input is a valid JSON string.50*/51goog.json.isValid = function(s) {52// All empty whitespace is not valid.53if (/^\s*$/.test(s)) {54return false;55}5657// This is taken from http://www.json.org/json2.js which is released to the58// public domain.59// Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator60// inside strings. We also treat \u2028 and \u2029 as whitespace which they61// are in the RFC but IE and Safari does not match \s to these so we need to62// include them in the reg exps in all places where whitespace is allowed.63// We allowed \x7f inside strings because some tools don't escape it,64// e.g. http://www.json.org/java/org/json/JSONObject.java6566// Parsing happens in three stages. In the first stage, we run the text67// against regular expressions that look for non-JSON patterns. We are68// especially concerned with '()' and 'new' because they can cause invocation,69// and '=' because it can cause mutation. But just to be safe, we want to70// reject all unexpected forms.7172// We split the first stage into 4 regexp operations in order to work around73// crippling inefficiencies in IE's and Safari's regexp engines. First we74// replace all backslash pairs with '@' (a non-JSON character). Second, we75// replace all simple value tokens with ']' characters, but only when followed76// by a colon, comma, closing bracket or end of string. Third, we delete all77// open brackets that follow a colon or comma or that begin the text. Finally,78// we look to see that the remaining characters are only whitespace or ']' or79// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.8081// Don't make these static since they have the global flag.82var backslashesRe = /\\["\\\/bfnrtu]/g;83var simpleValuesRe =84/(?:"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)[\s\u2028\u2029]*(?=:|,|]|}|$)/g;85var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;86var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;8788return remainderRe.test(89s.replace(backslashesRe, '@')90.replace(simpleValuesRe, ']')91.replace(openBracketsRe, ''));92};9394/**95* Logs a parsing error in {@code JSON.parse} solvable by using {@code eval}96* if {@code goog.json.TRY_NATIVE_JSON} is enabled.97* @private {function(string, !Error)} The first parameter is the error message,98* the second is the exception thrown by {@code JSON.parse}.99*/100goog.json.errorLogger_ = goog.nullFunction;101102103/**104* Sets an error logger to use if there's a recoverable parsing error and {@code105* goog.json.TRY_NATIVE_JSON} is enabled.106* @param {function(string, !Error)} errorLogger The first parameter is the107* error message, the second is the exception thrown by {@code JSON.parse}.108*/109goog.json.setErrorLogger = function(errorLogger) {110goog.json.errorLogger_ = errorLogger;111};112113114/**115* Parses a JSON string and returns the result. This throws an exception if116* the string is an invalid JSON string.117*118* Note that this is very slow on large strings. If you trust the source of119* the string then you should use unsafeParse instead.120*121* @param {*} s The JSON string to parse.122* @throws Error if s is invalid JSON.123* @return {Object} The object generated from the JSON string, or null.124*/125goog.json.parse = goog.json.USE_NATIVE_JSON ?126/** @type {function(*):Object} */ (goog.global['JSON']['parse']) :127function(s) {128var error;129if (goog.json.TRY_NATIVE_JSON) {130try {131return goog.global['JSON']['parse'](s);132} catch (ex) {133error = ex;134}135}136var o = String(s);137if (goog.json.isValid(o)) {138139try {140var result = /** @type {?Object} */ (eval('(' + o + ')'));141if (error) {142goog.json.errorLogger_('Invalid JSON: ' + o, error);143}144return result;145} catch (ex) {146}147}148throw Error('Invalid JSON string: ' + o);149};150151152/**153* Parses a JSON string and returns the result. This uses eval so it is open154* to security issues and it should only be used if you trust the source.155*156* @param {string} s The JSON string to parse.157* @return {Object} The object generated from the JSON string.158* @deprecated Use JSON.parse if possible or goog.json.parse.159*/160goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?161/** @type {function(string):Object} */ (goog.global['JSON']['parse']) :162function(s) {163var error;164if (goog.json.TRY_NATIVE_JSON) {165try {166return goog.global['JSON']['parse'](s);167} catch (ex) {168error = ex;169}170}171var result = /** @type {?Object} */ (eval('(' + s + ')'));172if (error) {173goog.json.errorLogger_('Invalid JSON: ' + s, error);174}175return result;176};177178179/**180* JSON replacer, as defined in Section 15.12.3 of the ES5 spec.181* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3182*183* TODO(nicksantos): Array should also be a valid replacer.184*185* @typedef {function(this:Object, string, *): *}186*/187goog.json.Replacer;188189190/**191* JSON reviver, as defined in Section 15.12.2 of the ES5 spec.192* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3193*194* @typedef {function(this:Object, string, *): *}195*/196goog.json.Reviver;197198199/**200* Serializes an object or a value to a JSON string.201*202* @param {*} object The object to serialize.203* @param {?goog.json.Replacer=} opt_replacer A replacer function204* called for each (key, value) pair that determines how the value205* should be serialized. By defult, this just returns the value206* and allows default serialization to kick in.207* @throws Error if there are loops in the object graph.208* @return {string} A JSON string representation of the input.209*/210goog.json.serialize = goog.json.USE_NATIVE_JSON ?211/** @type {function(*, ?goog.json.Replacer=):string} */212(goog.global['JSON']['stringify']) :213function(object, opt_replacer) {214// NOTE(nicksantos): Currently, we never use JSON.stringify.215//216// The last time I evaluated this, JSON.stringify had subtle bugs and217// behavior differences on all browsers, and the performance win was not218// large enough to justify all the issues. This may change in the future219// as browser implementations get better.220//221// assertSerialize in json_test contains if branches for the cases222// that fail.223return new goog.json.Serializer(opt_replacer).serialize(object);224};225226227228/**229* Class that is used to serialize JSON objects to a string.230* @param {?goog.json.Replacer=} opt_replacer Replacer.231* @constructor232*/233goog.json.Serializer = function(opt_replacer) {234/**235* @type {goog.json.Replacer|null|undefined}236* @private237*/238this.replacer_ = opt_replacer;239};240241242/**243* Serializes an object or a value to a JSON string.244*245* @param {*} object The object to serialize.246* @throws Error if there are loops in the object graph.247* @return {string} A JSON string representation of the input.248*/249goog.json.Serializer.prototype.serialize = function(object) {250var sb = [];251this.serializeInternal(object, sb);252return sb.join('');253};254255256/**257* Serializes a generic value to a JSON string258* @protected259* @param {*} object The object to serialize.260* @param {Array<string>} sb Array used as a string builder.261* @throws Error if there are loops in the object graph.262*/263goog.json.Serializer.prototype.serializeInternal = function(object, sb) {264if (object == null) {265// undefined == null so this branch covers undefined as well as null266sb.push('null');267return;268}269270if (typeof object == 'object') {271if (goog.isArray(object)) {272this.serializeArray(object, sb);273return;274} else if (275object instanceof String || object instanceof Number ||276object instanceof Boolean) {277object = object.valueOf();278// Fall through to switch below.279} else {280this.serializeObject_(/** @type {!Object} */ (object), sb);281return;282}283}284285switch (typeof object) {286case 'string':287this.serializeString_(object, sb);288break;289case 'number':290this.serializeNumber_(object, sb);291break;292case 'boolean':293sb.push(String(object));294break;295case 'function':296sb.push('null');297break;298default:299throw Error('Unknown type: ' + typeof object);300}301};302303304/**305* Character mappings used internally for goog.string.quote306* @private307* @type {!Object}308*/309goog.json.Serializer.charToJsonCharCache_ = {310'\"': '\\"',311'\\': '\\\\',312'/': '\\/',313'\b': '\\b',314'\f': '\\f',315'\n': '\\n',316'\r': '\\r',317'\t': '\\t',318319'\x0B': '\\u000b' // '\v' is not supported in JScript320};321322323/**324* Regular expression used to match characters that need to be replaced.325* The S60 browser has a bug where unicode characters are not matched by326* regular expressions. The condition below detects such behaviour and327* adjusts the regular expression accordingly.328* @private329* @type {!RegExp}330*/331goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?332/[\\\"\x00-\x1f\x7f-\uffff]/g :333/[\\\"\x00-\x1f\x7f-\xff]/g;334335336/**337* Serializes a string to a JSON string338* @private339* @param {string} s The string to serialize.340* @param {Array<string>} sb Array used as a string builder.341*/342goog.json.Serializer.prototype.serializeString_ = function(s, sb) {343// The official JSON implementation does not work with international344// characters.345sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {346// caching the result improves performance by a factor 2-3347var rv = goog.json.Serializer.charToJsonCharCache_[c];348if (!rv) {349rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).substr(1);350goog.json.Serializer.charToJsonCharCache_[c] = rv;351}352return rv;353}), '"');354};355356357/**358* Serializes a number to a JSON string359* @private360* @param {number} n The number to serialize.361* @param {Array<string>} sb Array used as a string builder.362*/363goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {364sb.push(isFinite(n) && !isNaN(n) ? String(n) : 'null');365};366367368/**369* Serializes an array to a JSON string370* @param {Array<string>} arr The array to serialize.371* @param {Array<string>} sb Array used as a string builder.372* @protected373*/374goog.json.Serializer.prototype.serializeArray = function(arr, sb) {375var l = arr.length;376sb.push('[');377var sep = '';378for (var i = 0; i < l; i++) {379sb.push(sep);380381var value = arr[i];382this.serializeInternal(383this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,384sb);385386sep = ',';387}388sb.push(']');389};390391392/**393* Serializes an object to a JSON string394* @private395* @param {!Object} obj The object to serialize.396* @param {Array<string>} sb Array used as a string builder.397*/398goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {399sb.push('{');400var sep = '';401for (var key in obj) {402if (Object.prototype.hasOwnProperty.call(obj, key)) {403var value = obj[key];404// Skip functions.405if (typeof value != 'function') {406sb.push(sep);407this.serializeString_(key, sb);408sb.push(':');409410this.serializeInternal(411this.replacer_ ? this.replacer_.call(obj, key, value) : value, sb);412413sep = ',';414}415}416}417sb.push('}');418};419420421