Skip to content

Commit

Permalink
[Fix] parse: allow parsing of empty keys in objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Coobaha authored and ljharb committed Mar 5, 2022
1 parent 066fae6 commit 1d5ccca
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 3 deletions.
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(withNull, {});
```

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

```javascript
var obj = qs.parse("=1&=2", { allowEmptyKeys: true });
assert.deepEqual(withNull, { "": ["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 @@ -83,6 +84,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 @@ -148,7 +152,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 @@ -168,7 +172,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 @@ -217,6 +221,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
35 changes: 34 additions & 1 deletion test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -847,10 +847,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();
});
});

0 comments on commit 1d5ccca

Please sign in to comment.