index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /**
  2. * Object#toString() ref for stringify().
  3. */
  4. var toString = Object.prototype.toString;
  5. /**
  6. * Object#hasOwnProperty ref
  7. */
  8. var hasOwnProperty = Object.prototype.hasOwnProperty;
  9. /**
  10. * Array#indexOf shim.
  11. */
  12. var indexOf = typeof Array.prototype.indexOf === 'function'
  13. ? function(arr, el) { return arr.indexOf(el); }
  14. : function(arr, el) {
  15. for (var i = 0; i < arr.length; i++) {
  16. if (arr[i] === el) return i;
  17. }
  18. return -1;
  19. };
  20. /**
  21. * Array.isArray shim.
  22. */
  23. var isArray = Array.isArray || function(arr) {
  24. return toString.call(arr) == '[object Array]';
  25. };
  26. /**
  27. * Object.keys shim.
  28. */
  29. var objectKeys = Object.keys || function(obj) {
  30. var ret = [];
  31. for (var key in obj) {
  32. if (obj.hasOwnProperty(key)) {
  33. ret.push(key);
  34. }
  35. }
  36. return ret;
  37. };
  38. /**
  39. * Array#forEach shim.
  40. */
  41. var forEach = typeof Array.prototype.forEach === 'function'
  42. ? function(arr, fn) { return arr.forEach(fn); }
  43. : function(arr, fn) {
  44. for (var i = 0; i < arr.length; i++) fn(arr[i]);
  45. };
  46. /**
  47. * Array#reduce shim.
  48. */
  49. var reduce = function(arr, fn, initial) {
  50. if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
  51. var res = initial;
  52. for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
  53. return res;
  54. };
  55. /**
  56. * Cache non-integer test regexp.
  57. */
  58. var isint = /^[0-9]+$/;
  59. function promote(parent, key) {
  60. if (parent[key].length == 0) return parent[key] = {}
  61. var t = {};
  62. for (var i in parent[key]) {
  63. if (hasOwnProperty.call(parent[key], i)) {
  64. t[i] = parent[key][i];
  65. }
  66. }
  67. parent[key] = t;
  68. return t;
  69. }
  70. function parse(parts, parent, key, val) {
  71. var part = parts.shift();
  72. // illegal
  73. if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
  74. // end
  75. if (!part) {
  76. if (isArray(parent[key])) {
  77. parent[key].push(val);
  78. } else if ('object' == typeof parent[key]) {
  79. parent[key] = val;
  80. } else if ('undefined' == typeof parent[key]) {
  81. parent[key] = val;
  82. } else {
  83. parent[key] = [parent[key], val];
  84. }
  85. // array
  86. } else {
  87. var obj = parent[key] = parent[key] || [];
  88. if (']' == part) {
  89. if (isArray(obj)) {
  90. if ('' != val) obj.push(val);
  91. } else if ('object' == typeof obj) {
  92. obj[objectKeys(obj).length] = val;
  93. } else {
  94. obj = parent[key] = [parent[key], val];
  95. }
  96. // prop
  97. } else if (~indexOf(part, ']')) {
  98. part = part.substr(0, part.length - 1);
  99. if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
  100. parse(parts, obj, part, val);
  101. // key
  102. } else {
  103. if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
  104. parse(parts, obj, part, val);
  105. }
  106. }
  107. }
  108. /**
  109. * Merge parent key/val pair.
  110. */
  111. function merge(parent, key, val){
  112. if (~indexOf(key, ']')) {
  113. var parts = key.split('[')
  114. , len = parts.length
  115. , last = len - 1;
  116. parse(parts, parent, 'base', val);
  117. // optimize
  118. } else {
  119. if (!isint.test(key) && isArray(parent.base)) {
  120. var t = {};
  121. for (var k in parent.base) t[k] = parent.base[k];
  122. parent.base = t;
  123. }
  124. set(parent.base, key, val);
  125. }
  126. return parent;
  127. }
  128. /**
  129. * Compact sparse arrays.
  130. */
  131. function compact(obj) {
  132. if ('object' != typeof obj) return obj;
  133. if (isArray(obj)) {
  134. var ret = [];
  135. for (var i in obj) {
  136. if (hasOwnProperty.call(obj, i)) {
  137. ret.push(obj[i]);
  138. }
  139. }
  140. return ret;
  141. }
  142. for (var key in obj) {
  143. obj[key] = compact(obj[key]);
  144. }
  145. return obj;
  146. }
  147. /**
  148. * Parse the given obj.
  149. */
  150. function parseObject(obj){
  151. var ret = { base: {} };
  152. forEach(objectKeys(obj), function(name){
  153. merge(ret, name, obj[name]);
  154. });
  155. return compact(ret.base);
  156. }
  157. /**
  158. * Parse the given str.
  159. */
  160. function parseString(str){
  161. var ret = reduce(String(str).split('&'), function(ret, pair){
  162. var eql = indexOf(pair, '=')
  163. , brace = lastBraceInKey(pair)
  164. , key = pair.substr(0, brace || eql)
  165. , val = pair.substr(brace || eql, pair.length)
  166. , val = val.substr(indexOf(val, '=') + 1, val.length);
  167. // ?foo
  168. if ('' == key) key = pair, val = '';
  169. if ('' == key) return ret;
  170. return merge(ret, decode(key), decode(val));
  171. }, { base: {} }).base;
  172. return compact(ret);
  173. }
  174. /**
  175. * Parse the given query `str` or `obj`, returning an object.
  176. *
  177. * @param {String} str | {Object} obj
  178. * @return {Object}
  179. * @api public
  180. */
  181. exports.parse = function(str){
  182. if (null == str || '' == str) return {};
  183. return 'object' == typeof str
  184. ? parseObject(str)
  185. : parseString(str);
  186. };
  187. /**
  188. * Turn the given `obj` into a query string
  189. *
  190. * @param {Object} obj
  191. * @return {String}
  192. * @api public
  193. */
  194. var stringify = exports.stringify = function(obj, prefix) {
  195. if (isArray(obj)) {
  196. return stringifyArray(obj, prefix);
  197. } else if ('[object Object]' == toString.call(obj)) {
  198. return stringifyObject(obj, prefix);
  199. } else if ('string' == typeof obj) {
  200. return stringifyString(obj, prefix);
  201. } else {
  202. return prefix + '=' + encodeURIComponent(String(obj));
  203. }
  204. };
  205. /**
  206. * Stringify the given `str`.
  207. *
  208. * @param {String} str
  209. * @param {String} prefix
  210. * @return {String}
  211. * @api private
  212. */
  213. function stringifyString(str, prefix) {
  214. if (!prefix) throw new TypeError('stringify expects an object');
  215. return prefix + '=' + encodeURIComponent(str);
  216. }
  217. /**
  218. * Stringify the given `arr`.
  219. *
  220. * @param {Array} arr
  221. * @param {String} prefix
  222. * @return {String}
  223. * @api private
  224. */
  225. function stringifyArray(arr, prefix) {
  226. var ret = [];
  227. if (!prefix) throw new TypeError('stringify expects an object');
  228. for (var i = 0; i < arr.length; i++) {
  229. ret.push(stringify(arr[i], prefix + '[' + i + ']'));
  230. }
  231. return ret.join('&');
  232. }
  233. /**
  234. * Stringify the given `obj`.
  235. *
  236. * @param {Object} obj
  237. * @param {String} prefix
  238. * @return {String}
  239. * @api private
  240. */
  241. function stringifyObject(obj, prefix) {
  242. var ret = []
  243. , keys = objectKeys(obj)
  244. , key;
  245. for (var i = 0, len = keys.length; i < len; ++i) {
  246. key = keys[i];
  247. if ('' == key) continue;
  248. if (null == obj[key]) {
  249. ret.push(encodeURIComponent(key) + '=');
  250. } else {
  251. ret.push(stringify(obj[key], prefix
  252. ? prefix + '[' + encodeURIComponent(key) + ']'
  253. : encodeURIComponent(key)));
  254. }
  255. }
  256. return ret.join('&');
  257. }
  258. /**
  259. * Set `obj`'s `key` to `val` respecting
  260. * the weird and wonderful syntax of a qs,
  261. * where "foo=bar&foo=baz" becomes an array.
  262. *
  263. * @param {Object} obj
  264. * @param {String} key
  265. * @param {String} val
  266. * @api private
  267. */
  268. function set(obj, key, val) {
  269. var v = obj[key];
  270. if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
  271. if (undefined === v) {
  272. obj[key] = val;
  273. } else if (isArray(v)) {
  274. v.push(val);
  275. } else {
  276. obj[key] = [v, val];
  277. }
  278. }
  279. /**
  280. * Locate last brace in `str` within the key.
  281. *
  282. * @param {String} str
  283. * @return {Number}
  284. * @api private
  285. */
  286. function lastBraceInKey(str) {
  287. var len = str.length
  288. , brace
  289. , c;
  290. for (var i = 0; i < len; ++i) {
  291. c = str[i];
  292. if (']' == c) brace = false;
  293. if ('[' == c) brace = true;
  294. if ('=' == c && !brace) return i;
  295. }
  296. }
  297. /**
  298. * Decode `str`.
  299. *
  300. * @param {String} str
  301. * @return {String}
  302. * @api private
  303. */
  304. function decode(str) {
  305. try {
  306. return decodeURIComponent(str.replace(/\+/g, ' '));
  307. } catch (err) {
  308. return str;
  309. }
  310. }