3 * Object#toString() ref for stringify().
6 var toString = Object.prototype.toString;
9 * Cache non-integer test regexp.
12 var isint = /^[0-9]+$/;
14 function promote(parent, key) {
15 if (parent[key].length == 0) return parent[key] = {};
17 for (var i in parent[key]) t[i] = parent[key][i];
22 function parse(parts, parent, key, val) {
23 var part = parts.shift();
26 if (Array.isArray(parent[key])) {
27 parent[key].push(val);
28 } else if ('object' == typeof parent[key]) {
30 } else if ('undefined' == typeof parent[key]) {
33 parent[key] = [parent[key], val];
37 var obj = parent[key] = parent[key] || [];
39 if (Array.isArray(obj)) {
40 if ('' != val) obj.push(val);
41 } else if ('object' == typeof obj) {
42 obj[Object.keys(obj).length] = val;
44 obj = parent[key] = [parent[key], val];
47 } else if (~part.indexOf(']')) {
48 part = part.substr(0, part.length - 1);
49 if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
50 parse(parts, obj, part, val);
53 if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
54 parse(parts, obj, part, val);
60 * Merge parent key/val pair.
63 function merge(parent, key, val){
64 if (~key.indexOf(']')) {
65 var parts = key.split('[')
68 parse(parts, parent, 'base', val);
71 if (!isint.test(key) && Array.isArray(parent.base)) {
73 for (var k in parent.base) t[k] = parent.base[k];
76 set(parent.base, key, val);
83 * Parse the given obj.
86 function parseObject(obj){
87 var ret = { base: {} };
88 Object.keys(obj).forEach(function(name){
89 merge(ret, name, obj[name]);
95 * Parse the given str.
98 function parseString(str){
101 .reduce(function(ret, pair){
102 var eql = pair.indexOf('=')
103 , brace = lastBraceInKey(pair)
104 , key = pair.substr(0, brace || eql)
105 , val = pair.substr(brace || eql, pair.length)
106 , val = val.substr(val.indexOf('=') + 1, val.length);
109 if ('' == key) key = pair, val = '';
111 return merge(ret, decode(key), decode(val));
112 }, { base: {} }).base;
116 * Parse the given query `str` or `obj`, returning an object.
118 * @param {String} str | {Object} obj
123 exports.parse = function(str){
124 if (null == str || '' == str) return {};
125 return 'object' == typeof str
131 * Turn the given `obj` into a query string
133 * @param {Object} obj
138 var stringify = exports.stringify = function(obj, prefix) {
139 if (Array.isArray(obj)) {
140 return stringifyArray(obj, prefix);
141 } else if ('[object Object]' == toString.call(obj)) {
142 return stringifyObject(obj, prefix);
143 } else if ('string' == typeof obj) {
144 return stringifyString(obj, prefix);
146 return prefix + '=' + obj;
151 * Stringify the given `str`.
153 * @param {String} str
154 * @param {String} prefix
159 function stringifyString(str, prefix) {
160 if (!prefix) throw new TypeError('stringify expects an object');
161 return prefix + '=' + encodeURIComponent(str);
165 * Stringify the given `arr`.
168 * @param {String} prefix
173 function stringifyArray(arr, prefix) {
175 if (!prefix) throw new TypeError('stringify expects an object');
176 for (var i = 0; i < arr.length; i++) {
177 ret.push(stringify(arr[i], prefix + '['+i+']'));
179 return ret.join('&');
183 * Stringify the given `obj`.
185 * @param {Object} obj
186 * @param {String} prefix
191 function stringifyObject(obj, prefix) {
193 , keys = Object.keys(obj)
196 for (var i = 0, len = keys.length; i < len; ++i) {
198 ret.push(stringify(obj[key], prefix
199 ? prefix + '[' + encodeURIComponent(key) + ']'
200 : encodeURIComponent(key)));
203 return ret.join('&');
207 * Set `obj`'s `key` to `val` respecting
208 * the weird and wonderful syntax of a qs,
209 * where "foo=bar&foo=baz" becomes an array.
211 * @param {Object} obj
212 * @param {String} key
213 * @param {String} val
217 function set(obj, key, val) {
219 if (undefined === v) {
221 } else if (Array.isArray(v)) {
229 * Locate last brace in `str` within the key.
231 * @param {String} str
236 function lastBraceInKey(str) {
240 for (var i = 0; i < len; ++i) {
242 if (']' == c) brace = false;
243 if ('[' == c) brace = true;
244 if ('=' == c && !brace) return i;
251 * @param {String} str
256 function decode(str) {
258 return decodeURIComponent(str.replace(/\+/g, ' '));