Rewrote QueryString.parse to make it smaller and more effective.
Also added ability to parse foo.bar=4 equal to foo[bar]=4 Added tests for this as well
This commit is contained in:
parent
0a8bd34b69
commit
f8ca6b383c
@ -1,7 +1,7 @@
|
||||
// Query String Utilities
|
||||
|
||||
var QueryString = exports;
|
||||
var urlDecode = process.binding('http_parser').urlDecode;
|
||||
var urlDecode = process.binding("http_parser").urlDecode;
|
||||
|
||||
// a safe fast alternative to decodeURIComponent
|
||||
QueryString.unescape = urlDecode;
|
||||
@ -26,24 +26,24 @@ var stack = [];
|
||||
* @static
|
||||
*/
|
||||
QueryString.stringify = QueryString.encode = function (obj, sep, eq, munge, name) {
|
||||
munge = typeof(munge) == "undefined" || munge;
|
||||
munge = typeof munge == "undefined" || munge;
|
||||
sep = sep || "&";
|
||||
eq = eq || "=";
|
||||
if (obj == null || typeof(obj) === 'function') {
|
||||
return name ? QueryString.escape(name) + eq : '';
|
||||
if (obj == null || typeof obj == "function") {
|
||||
return name ? QueryString.escape(name) + eq : "";
|
||||
}
|
||||
|
||||
if (isBool(obj)) obj = +obj;
|
||||
if (isBool(obj)) {
|
||||
obj = +obj;
|
||||
}
|
||||
if (isNumber(obj) || isString(obj)) {
|
||||
return QueryString.escape(name) + eq + QueryString.escape(obj);
|
||||
}
|
||||
if (isA(obj, [])) {
|
||||
var s = [];
|
||||
name = name+(munge ? '[]' : '');
|
||||
for (var i = 0, l = obj.length; i < l; i ++) {
|
||||
s.push( QueryString.stringify(obj[i], sep, eq, munge, name) );
|
||||
}
|
||||
return s.join(sep);
|
||||
name = name + (munge ? "[]" : "");
|
||||
return obj.map(function (item) {
|
||||
return QueryString.stringify(item, sep, eq, munge, name);
|
||||
}).join(sep);
|
||||
}
|
||||
// now we know it's an object.
|
||||
|
||||
@ -54,93 +54,61 @@ QueryString.stringify = QueryString.encode = function (obj, sep, eq, munge, name
|
||||
|
||||
stack.push(obj);
|
||||
|
||||
var s = [];
|
||||
var begin = name ? name + '[' : '';
|
||||
var end = name ? ']' : '';
|
||||
var keys = Object.keys(obj);
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
var key = keys[i];
|
||||
var n = begin + key + end;
|
||||
s.push(QueryString.stringify(obj[key], sep, eq, munge, n));
|
||||
}
|
||||
var begin = name ? name + "[" : "",
|
||||
end = name ? "]" : "",
|
||||
keys = Object.keys(obj),
|
||||
n,
|
||||
s = Object.keys(obj).map(function (key) {
|
||||
n = begin + key + end;
|
||||
return QueryString.stringify(obj[key], sep, eq, munge, n);
|
||||
}).join(sep);
|
||||
|
||||
stack.pop();
|
||||
|
||||
s = s.join(sep);
|
||||
if (!s && name) return name + "=";
|
||||
if (!s && name) {
|
||||
return name + "=";
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
QueryString.parse = QueryString.decode = function (qs, sep, eq) {
|
||||
return (qs || '')
|
||||
.split(sep||"&")
|
||||
.map(pieceParser(eq||"="))
|
||||
.reduce(mergeParams);
|
||||
};
|
||||
|
||||
// matches .xxxxx or [xxxxx] or ['xxxxx'] or ["xxxxx"] with optional [] at the end
|
||||
var chunks = /(?:(?:^|\.)([^\[\(\.]+)(?=\[|\.|$|\()|\[([^"'][^\]]*?)\]|\["([^\]"]*?)"\]|\['([^\]']*?)'\])(\[\])?/g;
|
||||
// Parse a key=val string.
|
||||
// These can get pretty hairy
|
||||
// example flow:
|
||||
// parse(foo[bar][][bla]=baz)
|
||||
// return parse(foo[bar][][bla],"baz")
|
||||
// return parse(foo[bar][], {bla : "baz"})
|
||||
// return parse(foo[bar], [{bla:"baz"}])
|
||||
// return parse(foo, {bar:[{bla:"baz"}]})
|
||||
// return {foo:{bar:[{bla:"baz"}]}}
|
||||
var slicerPattern = /(.*)\[([^\]]*)\]$/;
|
||||
var pieceParser = function (eq) {
|
||||
return function parsePiece (key, val) {
|
||||
if (arguments.length !== 2) {
|
||||
// key=val, called from the map/reduce
|
||||
key = key.split(eq);
|
||||
return parsePiece(QueryString.unescape(key.shift(), true),
|
||||
QueryString.unescape(key.join(eq), true));
|
||||
QueryString.parse = QueryString.decode = function (qs, sep, eq) {
|
||||
var obj = {};
|
||||
String(qs).split(sep || "&").map(function (keyValue) {
|
||||
var res = obj,
|
||||
next,
|
||||
kv = keyValue.split(eq || "="),
|
||||
key = QueryString.unescape(kv.shift(), true),
|
||||
value = QueryString.unescape(kv.join(eq || "="), true);
|
||||
key.replace(chunks, function (all, name, nameInBrackets, nameIn2Quotes, nameIn1Quotes, isArray, offset) {
|
||||
var end = offset + all.length == key.length;
|
||||
name = name || nameInBrackets || nameIn2Quotes || nameIn1Quotes;
|
||||
next = end ? value : {};
|
||||
next = next && (+next == next ? +next : next);
|
||||
if (Array.isArray(res[name])) {
|
||||
res[name].push(next);
|
||||
res = next;
|
||||
} else {
|
||||
if (name in res) {
|
||||
if (isArray || end) {
|
||||
res = (res[name] = [res[name], next])[1];
|
||||
} else {
|
||||
res = res[name];
|
||||
}
|
||||
var sliced = slicerPattern.exec(key);
|
||||
if (!sliced) {
|
||||
var ret = {};
|
||||
if (key) ret[key] = val;
|
||||
return ret;
|
||||
} else {
|
||||
if (isArray) {
|
||||
res = (res[name] = [next])[0];
|
||||
} else {
|
||||
res = res[name] = next;
|
||||
}
|
||||
// ["foo[][bar][][baz]", "foo[][bar][]", "baz"]
|
||||
var tail = sliced[2], head = sliced[1];
|
||||
|
||||
// array: key[]=val
|
||||
if (!tail) return parsePiece(head, [val]);
|
||||
|
||||
// obj: key[subkey]=val
|
||||
var ret = {};
|
||||
ret[tail] = val;
|
||||
return parsePiece(head, ret);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
|
||||
// the reducer function that merges each query piece together into one set of params
|
||||
function mergeParams (params, addition) {
|
||||
return (
|
||||
// if it's uncontested, then just return the addition.
|
||||
(!params) ? addition
|
||||
// if the existing value is an array, then concat it.
|
||||
: (isA(params, [])) ? params.concat(addition)
|
||||
// if the existing value is not an array, and either are not objects, arrayify it.
|
||||
: (!isA(params, {}) || !isA(addition, {})) ? [params].concat(addition)
|
||||
// else merge them as objects, which is a little more complex
|
||||
: mergeObjects(params, addition)
|
||||
);
|
||||
}
|
||||
|
||||
// Merge two *objects* together. If this is called, we've already ruled
|
||||
// out the simple cases, and need to do a loop.
|
||||
function mergeObjects (params, addition) {
|
||||
var keys = Object.keys(addition);
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
var key = keys[i];
|
||||
if (key) {
|
||||
params[key] = mergeParams(params[key], addition[key]);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function isA (thing, canon) {
|
||||
// special case for null and undefined
|
||||
|
@ -29,6 +29,15 @@ var qsTestCases = [
|
||||
["foo[bar][bla]=baz&foo[bar][bla]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
["foo[bar][][bla]=baz&foo[bar][][bla]=blo", "foo%5Bbar%5D%5B%5D%5Bbla%5D=baz&foo%5Bbar%5D%5B%5D%5Bbla%5D=blo", {"foo":{"bar":[{"bla":"baz"},{"bla":"blo"}]}}],
|
||||
["foo[bar][bla][]=baz&foo[bar][bla][]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
|
||||
["foo.bar.bla=baz&foo.bar.bla=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
["foo.bar[].bla=baz&foo[bar][][bla]=blo", "foo%5Bbar%5D%5B%5D%5Bbla%5D=baz&foo%5Bbar%5D%5B%5D%5Bbla%5D=blo", {"foo":{"bar":[{"bla":"baz"},{"bla":"blo"}]}}],
|
||||
["foo[bar].bla[]=baz&foo.bar[bla][]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
|
||||
["foo['bar']['bla']=baz&foo[\"bar\"][\"bla\"]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
["foo['bar'][]['bla']=baz&foo['bar'][][\"bla\"]=blo", "foo%5Bbar%5D%5B%5D%5Bbla%5D=baz&foo%5Bbar%5D%5B%5D%5Bbla%5D=blo", {"foo":{"bar":[{"bla":"baz"},{"bla":"blo"}]}}],
|
||||
["foo[bar][\"bla\"][]=baz&foo[\"bar\"][bla][]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
|
||||
|
||||
[" foo = bar ", "%20foo%20=%20bar%20", {" foo ":" bar "}],
|
||||
["foo=%zx", "foo=%25zx", {"foo":"%zx"}],
|
||||
["foo=%EF%BF%BD", "foo=%EF%BF%BD", {"foo" : "\ufffd" }]
|
||||
|
Loading…
x
Reference in New Issue
Block a user