json.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*
  2. Copyright (c) 2011, Yahoo! Inc. All rights reserved.
  3. Code licensed under the BSD License:
  4. http://developer.yahoo.com/yui/license.html
  5. version: 2.9.0
  6. */
  7. /**
  8. * Provides methods to parse JSON strings and convert objects to JSON strings.
  9. *
  10. * @module json
  11. * @class JSON
  12. * @namespace YAHOO.lang
  13. * @static
  14. */
  15. (function () {
  16. var l = YAHOO.lang,
  17. isFunction = l.isFunction,
  18. isObject = l.isObject,
  19. isArray = l.isArray,
  20. _toStr = Object.prototype.toString,
  21. // 'this' is the global object. window in browser env. Keep
  22. // the code env agnostic. Caja requies window, unfortunately.
  23. Native = (YAHOO.env.ua.caja ? window : this).JSON,
  24. /* Variables used by parse */
  25. /**
  26. * Replace certain Unicode characters that JavaScript may handle incorrectly
  27. * during eval--either by deleting them or treating them as line
  28. * endings--with escape sequences.
  29. * IMPORTANT NOTE: This regex will be used to modify the input if a match is
  30. * found.
  31. *
  32. * @property _UNICODE_EXCEPTIONS
  33. * @type {RegExp}
  34. * @private
  35. */
  36. _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  37. /**
  38. * First step in the safety evaluation. Regex used to replace all escape
  39. * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
  40. *
  41. * @property _ESCAPES
  42. * @type {RegExp}
  43. * @static
  44. * @private
  45. */
  46. _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
  47. /**
  48. * Second step in the safety evaluation. Regex used to replace all simple
  49. * values with ']' characters.
  50. *
  51. * @property _VALUES
  52. * @type {RegExp}
  53. * @static
  54. * @private
  55. */
  56. _VALUES = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
  57. /**
  58. * Third step in the safety evaluation. Regex used to remove all open
  59. * square brackets following a colon, comma, or at the beginning of the
  60. * string.
  61. *
  62. * @property _BRACKETS
  63. * @type {RegExp}
  64. * @static
  65. * @private
  66. */
  67. _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g,
  68. /**
  69. * Final step in the safety evaluation. Regex used to test the string left
  70. * after all previous replacements for invalid characters.
  71. *
  72. * @property _UNSAFE
  73. * @type {RegExp}
  74. * @static
  75. * @private
  76. */
  77. _UNSAFE = /[^\],:{}\s]/,
  78. /* Variables used by stringify */
  79. /**
  80. * Regex used to replace special characters in strings for JSON
  81. * stringification.
  82. *
  83. * @property _SPECIAL_CHARS
  84. * @type {RegExp}
  85. * @static
  86. * @private
  87. */
  88. _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  89. /**
  90. * Character substitution map for common escapes and special characters.
  91. *
  92. * @property _CHARS
  93. * @type {Object}
  94. * @static
  95. * @private
  96. */
  97. _CHARS = {
  98. '\b': '\\b',
  99. '\t': '\\t',
  100. '\n': '\\n',
  101. '\f': '\\f',
  102. '\r': '\\r',
  103. '"' : '\\"',
  104. '\\': '\\\\'
  105. },
  106. UNDEFINED = 'undefined',
  107. OBJECT = 'object',
  108. NULL = 'null',
  109. STRING = 'string',
  110. NUMBER = 'number',
  111. BOOLEAN = 'boolean',
  112. DATE = 'date',
  113. _allowable = {
  114. 'undefined' : UNDEFINED,
  115. 'string' : STRING,
  116. '[object String]' : STRING,
  117. 'number' : NUMBER,
  118. '[object Number]' : NUMBER,
  119. 'boolean' : BOOLEAN,
  120. '[object Boolean]' : BOOLEAN,
  121. '[object Date]' : DATE,
  122. '[object RegExp]' : OBJECT
  123. },
  124. EMPTY = '',
  125. OPEN_O = '{',
  126. CLOSE_O = '}',
  127. OPEN_A = '[',
  128. CLOSE_A = ']',
  129. COMMA = ',',
  130. COMMA_CR = ",\n",
  131. CR = "\n",
  132. COLON = ':',
  133. COLON_SP = ': ',
  134. QUOTE = '"';
  135. // Only accept JSON objects that report a [[Class]] of JSON
  136. Native = _toStr.call(Native) === '[object JSON]' && Native;
  137. // Escapes a special character to a safe Unicode representation
  138. function _char(c) {
  139. if (!_CHARS[c]) {
  140. _CHARS[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
  141. }
  142. return _CHARS[c];
  143. }
  144. /* functions used by parse */
  145. /**
  146. * Traverses nested objects, applying a filter or reviver function to
  147. * each value. The value returned from the function will replace the
  148. * original value in the key:value pair. If the value returned is
  149. * undefined, the key will be omitted from the returned object.
  150. *
  151. * @method _revive
  152. * @param data {MIXED} Any JavaScript data
  153. * @param reviver {Function} filter or mutation function
  154. * @return {MIXED} The results of the filtered/mutated data structure
  155. * @private
  156. */
  157. function _revive(data, reviver) {
  158. var walk = function (o,key) {
  159. var k,v,value = o[key];
  160. if (value && typeof value === 'object') {
  161. for (k in value) {
  162. if (l.hasOwnProperty(value,k)) {
  163. v = walk(value, k);
  164. if (v === undefined) {
  165. delete value[k];
  166. } else {
  167. value[k] = v;
  168. }
  169. }
  170. }
  171. }
  172. return reviver.call(o,key,value);
  173. };
  174. return typeof reviver === 'function' ? walk({'':data},'') : data;
  175. }
  176. /**
  177. * Replace certain Unicode characters that may be handled incorrectly by
  178. * some browser implementations.
  179. *
  180. * @method _prepare
  181. * @param s {String} parse input
  182. * @return {String} sanitized JSON string ready to be validated/parsed
  183. * @private
  184. */
  185. function _prepare(s) {
  186. return s.replace(_UNICODE_EXCEPTIONS, _char);
  187. }
  188. function _isSafe(str) {
  189. return l.isString(str) &&
  190. !_UNSAFE.test(str.replace(_ESCAPES,'@').
  191. replace(_VALUES,']').
  192. replace(_BRACKETS,''));
  193. }
  194. function _parse(s,reviver) {
  195. // sanitize
  196. s = _prepare(s);
  197. // Ensure valid JSON
  198. if (_isSafe(s)) {
  199. // Eval the text into a JavaScript data structure, apply the
  200. // reviver function if provided, and return
  201. return _revive( eval('(' + s + ')'), reviver );
  202. }
  203. // The text is not valid JSON
  204. throw new SyntaxError('JSON.parse');
  205. }
  206. /* functions used by stringify */
  207. // Utility function used to determine how to serialize a variable.
  208. function _type(o) {
  209. var t = typeof o;
  210. return _allowable[t] || // number, string, boolean, undefined
  211. _allowable[_toStr.call(o)] || // Number, String, Boolean, Date
  212. (t === OBJECT ?
  213. (o ? OBJECT : NULL) : // object, array, null, misc natives
  214. UNDEFINED); // function, unknown
  215. }
  216. // Enclose escaped strings in quotes
  217. function _string(s) {
  218. return QUOTE + s.replace(_SPECIAL_CHARS, _char) + QUOTE;
  219. }
  220. // Adds the provided space to the beginning of every line in the input string
  221. function _indent(s,space) {
  222. return s.replace(/^/gm, space);
  223. }
  224. // JavaScript implementation of stringify (see API declaration of stringify)
  225. function _stringify(o,w,space) {
  226. if (o === undefined) {
  227. return undefined;
  228. }
  229. var replacer = isFunction(w) ? w : null,
  230. format = _toStr.call(space).match(/String|Number/) || [],
  231. _date = YAHOO.lang.JSON.dateToString,
  232. stack = [],
  233. tmp,i,len;
  234. if (replacer || !isArray(w)) {
  235. w = undefined;
  236. }
  237. // Ensure whitelist keys are unique (bug 2110391)
  238. if (w) {
  239. tmp = {};
  240. for (i = 0, len = w.length; i < len; ++i) {
  241. tmp[w[i]] = true;
  242. }
  243. w = tmp;
  244. }
  245. // Per the spec, strings are truncated to 10 characters and numbers
  246. // are converted to that number of spaces (max 10)
  247. space = format[0] === 'Number' ?
  248. new Array(Math.min(Math.max(0,space),10)+1).join(" ") :
  249. (space || EMPTY).slice(0,10);
  250. function _serialize(h,key) {
  251. var value = h[key],
  252. t = _type(value),
  253. a = [],
  254. colon = space ? COLON_SP : COLON,
  255. arr, i, keys, k, v;
  256. // Per the ECMA 5 spec, toJSON is applied before the replacer is
  257. // called. Also per the spec, Date.prototype.toJSON has been added, so
  258. // Date instances should be serialized prior to exposure to the
  259. // replacer. I disagree with this decision, but the spec is the spec.
  260. if (isObject(value) && isFunction(value.toJSON)) {
  261. value = value.toJSON(key);
  262. } else if (t === DATE) {
  263. value = _date(value);
  264. }
  265. if (isFunction(replacer)) {
  266. value = replacer.call(h,key,value);
  267. }
  268. if (value !== h[key]) {
  269. t = _type(value);
  270. }
  271. switch (t) {
  272. case DATE : // intentional fallthrough. Pre-replacer Dates are
  273. // serialized in the toJSON stage. Dates here would
  274. // have been produced by the replacer.
  275. case OBJECT : break;
  276. case STRING : return _string(value);
  277. case NUMBER : return isFinite(value) ? value+EMPTY : NULL;
  278. case BOOLEAN : return value+EMPTY;
  279. case NULL : return NULL;
  280. default : return undefined;
  281. }
  282. // Check for cyclical references in nested objects
  283. for (i = stack.length - 1; i >= 0; --i) {
  284. if (stack[i] === value) {
  285. throw new Error("JSON.stringify. Cyclical reference");
  286. }
  287. }
  288. arr = isArray(value);
  289. // Add the object to the processing stack
  290. stack.push(value);
  291. if (arr) { // Array
  292. for (i = value.length - 1; i >= 0; --i) {
  293. a[i] = _serialize(value, i) || NULL;
  294. }
  295. } else { // Object
  296. // If whitelist provided, take only those keys
  297. keys = w || value;
  298. i = 0;
  299. for (k in keys) {
  300. if (l.hasOwnProperty(keys, k)) {
  301. v = _serialize(value, k);
  302. if (v) {
  303. a[i++] = _string(k) + colon + v;
  304. }
  305. }
  306. }
  307. }
  308. // remove the array from the stack
  309. stack.pop();
  310. if (space && a.length) {
  311. return arr ?
  312. OPEN_A + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_A :
  313. OPEN_O + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_O;
  314. } else {
  315. return arr ?
  316. OPEN_A + a.join(COMMA) + CLOSE_A :
  317. OPEN_O + a.join(COMMA) + CLOSE_O;
  318. }
  319. }
  320. // process the input
  321. return _serialize({'':o},'');
  322. }
  323. /* Public API */
  324. YAHOO.lang.JSON = {
  325. /**
  326. * Leverage native JSON parse if the browser has a native implementation.
  327. * In general, this is a good idea. See the Known Issues section in the
  328. * JSON user guide for caveats. The default value is true for browsers with
  329. * native JSON support.
  330. *
  331. * @property useNativeParse
  332. * @type Boolean
  333. * @default true
  334. * @static
  335. */
  336. useNativeParse : !!Native,
  337. /**
  338. * Leverage native JSON stringify if the browser has a native
  339. * implementation. In general, this is a good idea. See the Known Issues
  340. * section in the JSON user guide for caveats. The default value is true
  341. * for browsers with native JSON support.
  342. *
  343. * @property useNativeStringify
  344. * @type Boolean
  345. * @default true
  346. * @static
  347. */
  348. useNativeStringify : !!Native,
  349. /**
  350. * Four step determination whether a string is safe to eval. In three steps,
  351. * escape sequences, safe values, and properly placed open square brackets
  352. * are replaced with placeholders or removed. Then in the final step, the
  353. * result of all these replacements is checked for invalid characters.
  354. *
  355. * @method isSafe
  356. * @param str {String} JSON string to be tested
  357. * @return {boolean} is the string safe for eval?
  358. * @static
  359. */
  360. isSafe : function (s) {
  361. return _isSafe(_prepare(s));
  362. },
  363. /**
  364. * <p>Parse a JSON string, returning the native JavaScript
  365. * representation.</p>
  366. *
  367. * <p>When lang.JSON.useNativeParse is true, this will defer to the native
  368. * JSON.parse if the browser has a native implementation. Otherwise, a
  369. * JavaScript implementation based on http://www.json.org/json2.js
  370. * is used.</p>
  371. *
  372. * @method parse
  373. * @param s {string} JSON string data
  374. * @param reviver {function} (optional) function(k,v) passed each key:value
  375. * pair of object literals, allowing pruning or altering values
  376. * @return {MIXED} the native JavaScript representation of the JSON string
  377. * @throws SyntaxError
  378. * @static
  379. */
  380. parse : function (s,reviver) {
  381. if (typeof s !== 'string') {
  382. s += '';
  383. }
  384. return Native && YAHOO.lang.JSON.useNativeParse ?
  385. Native.parse(s,reviver) : _parse(s,reviver);
  386. },
  387. /**
  388. * <p>Converts an arbitrary value to a JSON string representation.</p>
  389. *
  390. * <p>Objects with cyclical references will trigger an exception.</p>
  391. *
  392. * <p>If a whitelist is provided, only matching object keys will be
  393. * included. Alternately, a replacer function may be passed as the
  394. * second parameter. This function is executed on every value in the
  395. * input, and its return value will be used in place of the original value.
  396. * This is useful to serialize specialized objects or class instances.</p>
  397. *
  398. * <p>If a positive integer or non-empty string is passed as the third
  399. * parameter, the output will be formatted with carriage returns and
  400. * indentation for readability. If a String is passed (such as "\t") it
  401. * will be used once for each indentation level. If a number is passed,
  402. * that number of spaces will be used.</p>
  403. *
  404. * <p>When lang.JSON.useNativeStringify is true, this will defer to the
  405. * native JSON.stringify if the browser has a native implementation.
  406. * Otherwise, a JavaScript implementation is used.</p>
  407. *
  408. * @method stringify
  409. * @param o {MIXED} any arbitrary object to convert to JSON string
  410. * @param w {Array|Function} (optional) whitelist of acceptable object keys
  411. * to include OR a function(value,key) to alter values
  412. * before serialization
  413. * @param space {Number|String} (optional) indentation character(s) or
  414. * depthy of spaces to format the output
  415. * @return {string} JSON string representation of the input
  416. * @throws Error
  417. * @static
  418. */
  419. stringify : function (o,w,space) {
  420. return Native && YAHOO.lang.JSON.useNativeStringify ?
  421. Native.stringify(o,w,space) : _stringify(o,w,space);
  422. },
  423. /**
  424. * Serializes a Date instance as a UTC date string. Used internally by
  425. * the JavaScript implementation of stringify. If you need a different
  426. * Date serialization format, override this method. If you change this,
  427. * you should also set useNativeStringify to false, since native JSON
  428. * implementations serialize Dates per the ECMAScript 5 spec. You've been
  429. * warned.
  430. *
  431. * @method dateToString
  432. * @param d {Date} The Date to serialize
  433. * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
  434. * @static
  435. */
  436. dateToString : function (d) {
  437. function _zeroPad(v) {
  438. return v < 10 ? '0' + v : v;
  439. }
  440. return d.getUTCFullYear() + '-' +
  441. _zeroPad(d.getUTCMonth() + 1) + '-' +
  442. _zeroPad(d.getUTCDate()) + 'T' +
  443. _zeroPad(d.getUTCHours()) + COLON +
  444. _zeroPad(d.getUTCMinutes()) + COLON +
  445. _zeroPad(d.getUTCSeconds()) + 'Z';
  446. },
  447. /**
  448. * Reconstitute Date instances from the default JSON UTC serialization.
  449. * Reference this from a reviver function to rebuild Dates during the
  450. * parse operation.
  451. *
  452. * @method stringToDate
  453. * @param str {String} String serialization of a Date
  454. * @return {Date}
  455. */
  456. stringToDate : function (str) {
  457. var m = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);
  458. if (m) {
  459. var d = new Date();
  460. d.setUTCFullYear(m[1], m[2]-1, m[3]);
  461. d.setUTCHours(m[4], m[5], m[6], (m[7] || 0));
  462. return d;
  463. }
  464. return str;
  465. }
  466. };
  467. /**
  468. * <p>Four step determination whether a string is safe to eval. In three steps,
  469. * escape sequences, safe values, and properly placed open square brackets
  470. * are replaced with placeholders or removed. Then in the final step, the
  471. * result of all these replacements is checked for invalid characters.</p>
  472. *
  473. * <p>This is an alias for isSafe.</p>
  474. *
  475. * @method isValid
  476. * @param str {String} JSON string to be tested
  477. * @return {boolean} is the string safe for eval?
  478. * @static
  479. * @deprecated use isSafe
  480. */
  481. YAHOO.lang.JSON.isValid = YAHOO.lang.JSON.isSafe;
  482. })();
  483. YAHOO.register("json", YAHOO.lang.JSON, {version: "2.9.0", build: "2800"});