Sin descripción

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