/hippo/src/main/webapp/yui/json/json.js

http://hdbc.googlecode.com/ · JavaScript · 396 lines · 177 code · 37 blank · 182 comment · 36 complexity · 0ebd899673df1d2177739ebc0094c831 MD5 · raw file

  1. /*
  2. Copyright (c) 2009, Yahoo! Inc. All rights reserved.
  3. Code licensed under the BSD License:
  4. http://developer.yahoo.net/yui/license.txt
  5. version: 2.7.0
  6. */
  7. /**
  8. * Provides methods to parse JSON strings and convert objects to JSON strings.
  9. * @module json
  10. * @class JSON
  11. * @static
  12. */
  13. YAHOO.lang.JSON = (function () {
  14. var l = YAHOO.lang,
  15. /**
  16. * Replace certain Unicode characters that JavaScript may handle incorrectly
  17. * during eval--either by deleting them or treating them as line
  18. * endings--with escape sequences.
  19. * IMPORTANT NOTE: This regex will be used to modify the input if a match is
  20. * found.
  21. * @property _UNICODE_EXCEPTIONS
  22. * @type {RegExp}
  23. * @private
  24. */
  25. _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  26. /**
  27. * First step in the validation. Regex used to replace all escape
  28. * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
  29. * @property _ESCAPES
  30. * @type {RegExp}
  31. * @static
  32. * @private
  33. */
  34. _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
  35. /**
  36. * Second step in the validation. Regex used to replace all simple
  37. * values with ']' characters.
  38. * @property _VALUES
  39. * @type {RegExp}
  40. * @static
  41. * @private
  42. */
  43. _VALUES = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
  44. /**
  45. * Third step in the validation. Regex used to remove all open square
  46. * brackets following a colon, comma, or at the beginning of the string.
  47. * @property _BRACKETS
  48. * @type {RegExp}
  49. * @static
  50. * @private
  51. */
  52. _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g,
  53. /**
  54. * Final step in the validation. Regex used to test the string left after
  55. * all previous replacements for invalid characters.
  56. * @property _INVALID
  57. * @type {RegExp}
  58. * @static
  59. * @private
  60. */
  61. _INVALID = /^[\],:{}\s]*$/,
  62. /**
  63. * Regex used to replace special characters in strings for JSON
  64. * stringification.
  65. * @property _SPECIAL_CHARS
  66. * @type {RegExp}
  67. * @static
  68. * @private
  69. */
  70. _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  71. /**
  72. * Character substitution map for common escapes and special characters.
  73. * @property _CHARS
  74. * @type {Object}
  75. * @static
  76. * @private
  77. */
  78. _CHARS = {
  79. '\b': '\\b',
  80. '\t': '\\t',
  81. '\n': '\\n',
  82. '\f': '\\f',
  83. '\r': '\\r',
  84. '"' : '\\"',
  85. '\\': '\\\\'
  86. };
  87. /**
  88. * Traverses nested objects, applying a filter or reviver function to
  89. * each value. The value returned from the function will replace the
  90. * original value in the key:value pair. If the value returned is
  91. * undefined, the key will be omitted from the returned object.
  92. * @method _revive
  93. * @param data {MIXED} Any JavaScript data
  94. * @param reviver {Function} filter or mutation function
  95. * @return {MIXED} The results of the filtered/mutated data structure
  96. * @private
  97. */
  98. function _revive(data, reviver) {
  99. var walk = function (o,key) {
  100. var k,v,value = o[key];
  101. if (value && typeof value === 'object') {
  102. for (k in value) {
  103. if (l.hasOwnProperty(value,k)) {
  104. v = walk(value, k);
  105. if (v === undefined) {
  106. delete value[k];
  107. } else {
  108. value[k] = v;
  109. }
  110. }
  111. }
  112. }
  113. return reviver.call(o,key,value);
  114. };
  115. return typeof reviver === 'function' ? walk({'':data},'') : data;
  116. }
  117. /**
  118. * Escapes a special character to a safe Unicode representation
  119. * @method _char
  120. * @param c {String} single character to escape
  121. * @return {String} safe Unicode escape
  122. */
  123. function _char(c) {
  124. if (!_CHARS[c]) {
  125. _CHARS[c] = '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
  126. }
  127. return _CHARS[c];
  128. }
  129. /**
  130. * Replace certain Unicode characters that may be handled incorrectly by
  131. * some browser implementations.
  132. * @method _prepare
  133. * @param s {String} parse input
  134. * @return {String} sanitized JSON string ready to be validated/parsed
  135. * @private
  136. */
  137. function _prepare(s) {
  138. return s.replace(_UNICODE_EXCEPTIONS, _char);
  139. }
  140. /**
  141. * Four step determination whether a string is valid JSON. In three steps,
  142. * escape sequences, safe values, and properly placed open square brackets
  143. * are replaced with placeholders or removed. Then in the final step, the
  144. * result of all these replacements is checked for invalid characters.
  145. * @method _isValid
  146. * @param str {String} JSON string to be tested
  147. * @return {boolean} is the string safe for eval?
  148. * @static
  149. */
  150. function _isValid(str) {
  151. return l.isString(str) &&
  152. _INVALID.test(str.
  153. replace(_ESCAPES,'@').
  154. replace(_VALUES,']').
  155. replace(_BRACKETS,''));
  156. }
  157. /**
  158. * Enclose escaped strings in quotes
  159. * @method _string
  160. * @param s {String} string to wrap
  161. * @return {String} '"'+s+'"' after s has had special characters escaped
  162. * @private
  163. */
  164. function _string(s) {
  165. return '"' + s.replace(_SPECIAL_CHARS, _char) + '"';
  166. }
  167. /**
  168. * Worker function used by public stringify.
  169. * @method _stringify
  170. * @param h {Object} object holding the key
  171. * @param key {String} String key in object h to serialize
  172. * @param depth {Number} depth to serialize
  173. * @param w {Array|Function} array of whitelisted keys OR replacer function
  174. * @param pstack {Array} used to protect against recursion
  175. * @return {String} serialized version of o
  176. */
  177. function _stringify(h,key,d,w,pstack) {
  178. var o = typeof w === 'function' ? w.call(h,key,h[key]) : h[key],
  179. i,len,j, // array iteration
  180. k,v, // object iteration
  181. isArray, // forking in typeof 'object'
  182. a; // composition array for performance over string concat
  183. if (o instanceof Date) {
  184. o = l.JSON.dateToString(o);
  185. } else if (o instanceof String || o instanceof Boolean || o instanceof Number) {
  186. o = o.valueOf();
  187. }
  188. switch (typeof o) {
  189. case 'string' : return _string(o);
  190. case 'number' : return isFinite(o) ? String(o) : 'null';
  191. case 'boolean': return String(o);
  192. case 'object' :
  193. // null
  194. if (o === null) {
  195. return 'null';
  196. }
  197. // Check for cyclical references
  198. for (i = pstack.length - 1; i >= 0; --i) {
  199. if (pstack[i] === o) {
  200. return 'null';
  201. }
  202. }
  203. // Add the object to the processing stack
  204. pstack[pstack.length] = o;
  205. a = [];
  206. isArray = l.isArray(o);
  207. // Only recurse if we're above depth config
  208. if (d > 0) {
  209. // Array
  210. if (isArray) {
  211. for (i = o.length - 1; i >= 0; --i) {
  212. a[i] = _stringify(o,i,d-1,w,pstack) || 'null';
  213. }
  214. // Object
  215. } else {
  216. j = 0;
  217. // Use whitelist keys if provided as an array
  218. if (l.isArray(w)) {
  219. for (i = 0, len = w.length; i < len; ++i) {
  220. k = w[i];
  221. v = _stringify(o,k,d-1,w,pstack);
  222. if (v) {
  223. a[j++] = _string(k) + ':' + v;
  224. }
  225. }
  226. } else {
  227. for (k in o) {
  228. if (typeof k === 'string' && l.hasOwnProperty(o,k)) {
  229. v = _stringify(o,k,d-1,w,pstack);
  230. if (v) {
  231. a[j++] = _string(k) + ':' + v;
  232. }
  233. }
  234. }
  235. }
  236. // sort object keys for easier readability
  237. a.sort();
  238. }
  239. }
  240. // remove the object from the stack
  241. pstack.pop();
  242. return isArray ? '['+a.join(',')+']' : '{'+a.join(',')+'}';
  243. }
  244. return undefined; // invalid input
  245. }
  246. // Return the public API
  247. return {
  248. /**
  249. * Four step determination whether a string is valid JSON. In three steps,
  250. * escape sequences, safe values, and properly placed open square brackets
  251. * are replaced with placeholders or removed. Then in the final step, the
  252. * result of all these replacements is checked for invalid characters.
  253. * @method isValid
  254. * @param str {String} JSON string to be tested
  255. * @return {boolean} is the string safe for eval?
  256. * @static
  257. */
  258. isValid : function (s) {
  259. return _isValid(_prepare(s));
  260. },
  261. /**
  262. * Parse a JSON string, returning the native JavaScript representation.
  263. * Only minor modifications from http://www.json.org/json2.js.
  264. * @param s {string} JSON string data
  265. * @param reviver {function} (optional) function(k,v) passed each key:value
  266. * pair of object literals, allowing pruning or altering values
  267. * @return {MIXED} the native JavaScript representation of the JSON string
  268. * @throws SyntaxError
  269. * @method parse
  270. * @static
  271. */
  272. parse : function (s,reviver) {
  273. // sanitize
  274. s = _prepare(s);
  275. // Ensure valid JSON
  276. if (_isValid(s)) {
  277. // Eval the text into a JavaScript data structure, apply the
  278. // reviver function if provided, and return
  279. return _revive( eval('(' + s + ')'), reviver );
  280. }
  281. // The text is not valid JSON
  282. throw new SyntaxError('parseJSON');
  283. },
  284. /**
  285. * Converts an arbitrary value to a JSON string representation.
  286. * Cyclical object or array references are replaced with null.
  287. * If a whitelist is provided, only matching object keys will be included.
  288. * If a depth limit is provided, objects and arrays at that depth will
  289. * be stringified as empty.
  290. * @method stringify
  291. * @param o {MIXED} any arbitrary object to convert to JSON string
  292. * @param w {Array|Function} (optional) whitelist of acceptable object keys to include OR a function(value,key) to alter values before serialization
  293. * @param d {number} (optional) depth limit to recurse objects/arrays (practical minimum 1)
  294. * @return {string} JSON string representation of the input
  295. * @static
  296. */
  297. stringify : function (o,w,d) {
  298. if (o !== undefined) {
  299. // Ensure whitelist keys are unique (bug 2110391)
  300. if (l.isArray(w)) {
  301. w = (function (a) {
  302. var uniq=[],map={},v,i,j,len;
  303. for (i=0,j=0,len=a.length; i<len; ++i) {
  304. v = a[i];
  305. if (typeof v === 'string' && map[v] === undefined) {
  306. uniq[(map[v] = j++)] = v;
  307. }
  308. }
  309. return uniq;
  310. })(w);
  311. }
  312. // Default depth to POSITIVE_INFINITY
  313. d = d >= 0 ? d : 1/0;
  314. // process the input
  315. return _stringify({'':o},'',d,w,[]);
  316. }
  317. return undefined;
  318. },
  319. /**
  320. * Serializes a Date instance as a UTC date string. Used internally by
  321. * stringify. Override this method if you need Dates serialized in a
  322. * different format.
  323. * @method dateToString
  324. * @param d {Date} The Date to serialize
  325. * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
  326. * @static
  327. */
  328. dateToString : function (d) {
  329. function _zeroPad(v) {
  330. return v < 10 ? '0' + v : v;
  331. }
  332. return d.getUTCFullYear() + '-' +
  333. _zeroPad(d.getUTCMonth() + 1) + '-' +
  334. _zeroPad(d.getUTCDate()) + 'T' +
  335. _zeroPad(d.getUTCHours()) + ':' +
  336. _zeroPad(d.getUTCMinutes()) + ':' +
  337. _zeroPad(d.getUTCSeconds()) + 'Z';
  338. },
  339. /**
  340. * Reconstitute Date instances from the default JSON UTC serialization.
  341. * Reference this from a reviver function to rebuild Dates during the
  342. * parse operation.
  343. * @method stringToDate
  344. * @param str {String} String serialization of a Date
  345. * @return {Date}
  346. */
  347. stringToDate : function (str) {
  348. if (/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.test(str)) {
  349. var d = new Date();
  350. d.setUTCFullYear(RegExp.$1, (RegExp.$2|0)-1, RegExp.$3);
  351. d.setUTCHours(RegExp.$4, RegExp.$5, RegExp.$6);
  352. return d;
  353. }
  354. return str;
  355. }
  356. };
  357. })();
  358. YAHOO.register("json", YAHOO.lang.JSON, {version: "2.7.0", build: "1799"});