json.js 13 KB

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