Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,23 @@ assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });

If you wish to auto-convert values which look like numbers, booleans, and other values into their primitive counterparts, you can use the [query-types Express JS middleware](https://github.com/xpepermint/query-types) which will auto-convert all request query parameters.


### Parsing empty keys

By default, empty keys are omitted after parsing:

```javascript
var obj = qs.parse("=1&=2");
assert.deepEqual(obj, {});
```

It is possible to include empty keys by using `allowEmptyKeys` flag:

```javascript
var obj = qs.parse("=1&=2", { allowEmptyKeys: true });
assert.deepEqual(obj, { "": ["1","2"] });
```

### Stringifying

[](#preventEval)
Expand Down
9 changes: 7 additions & 2 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var isArray = Array.isArray;

var defaults = {
allowDots: false,
allowEmptyKeys: false,
allowPrototypes: false,
allowSparse: false,
arrayLimit: 20,
Expand Down Expand Up @@ -84,6 +85,9 @@ var parseValues = function parseQueryStringValues(str, options) {
var key, val;
if (pos === -1) {
key = options.decoder(part, defaults.decoder, charset, 'key');
if (key === '') {
continue;
}
val = options.strictNullHandling ? null : '';
} else {
key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
Expand Down Expand Up @@ -149,7 +153,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
};

var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
if (!givenKey) {
if (!givenKey && (!options.allowEmptyKeys || givenKey !== '')) {
return;
}

Expand All @@ -169,7 +173,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesPars
// Stash the parent if it exists

var keys = [];
if (parent) {
if (parent || (options.allowEmptyKeys && parent === '')) {
// If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties
if (!options.plainObjects && has.call(Object.prototype, parent)) {
if (!options.allowPrototypes) {
Expand Down Expand Up @@ -218,6 +222,7 @@ var normalizeParseOptions = function normalizeParseOptions(opts) {

return {
allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
allowEmptyKeys: typeof opts.allowEmptyKeys === 'undefined' ? defaults.allowEmptyKeys : !!opts.allowEmptyKeys,
allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,
allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,
arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,
Expand Down
54 changes: 27 additions & 27 deletions test/empty-keys-cases.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,10 +889,43 @@ test('parse()', function (t) {

test('parses empty keys', function (t) {
emptyTestCases.forEach(function (testCase) {
t.test('parses an object with empty string key with ' + testCase.input, function (st) {
st.deepEqual(qs.parse(testCase.input, { allowEmptyKeys: true }), testCase.withEmptyKeys);
st.deepEqual(qs.parse(testCase.stringifyOutput, { allowEmptyKeys: true }), testCase.withEmptyKeys);

st.end();
});

t.test('skips empty string key with ' + testCase.input, function (st) {
st.deepEqual(qs.parse(testCase.input), testCase.noEmptyKeys);
st.deepEqual(
qs.parse(testCase.input),
testCase.noEmptyKeys
);

st.deepEqual(
qs.parse(testCase.input, { allowEmptyKeys: false }),
testCase.noEmptyKeys
);

st.end();
});
});

t.test('edge case with object/arrays', function (st) {
st.deepEqual(
qs.parse('[][0]=2&[][1]=3', { allowEmptyKeys: true }),
{ '': { '': ['2', '3'] } },
'array/object conversion',
{ skip: 'TODO: figure out what this should do' }
);

st.deepEqual(
qs.parse('[][0]=2&[][1]=3&[a]=2', { allowEmptyKeys: true }),
{ '': { '': ['2', '3'], a: '2' } },
'array/object conversion',
{ skip: 'TODO: figure out what this should do' }
);

st.end();
});
});
1 change: 1 addition & 0 deletions test/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ test('stringifies empty keys', function (t) {
emptyTestCases.forEach(function (testCase) {
t.test('stringifies an object with empty string key with ' + testCase.input, function (st) {
st.deepEqual(qs.stringify(testCase.withEmptyKeys, { encode: false }), testCase.stringifyOutput);
st.deepEqual(qs.stringify(testCase.noEmptyKeys, { encode: false }), testCase.stringifyOutputNoEmpty);

st.end();
});
Expand Down