Comparing version
@@ -0,1 +1,24 @@ | ||
## **6.12.0** | ||
- [New] `parse`/`stringify`: add `decodeDotInKeys`/`encodeDotKeys` options (#488) | ||
- [New] `parse`: add `duplicates` option | ||
- [New] `parse`/`stringify`: add `allowEmptyArrays` option to allow [] in object values (#487) | ||
- [Refactor] `parse`/`stringify`: move allowDots config logic to its own variable | ||
- [Refactor] `stringify`: move option-handling code into `normalizeStringifyOptions` | ||
- [readme] update readme, add logos (#484) | ||
- [readme] `stringify`: clarify default `arrayFormat` behavior | ||
- [readme] fix line wrapping | ||
- [readme] remove dead badges | ||
- [Deps] update `side-channel` | ||
- [meta] make the dist build 50% smaller | ||
- [meta] add `sideEffects` flag | ||
- [meta] run build in prepack, not prepublish | ||
- [Tests] `parse`: remove useless tests; add coverage | ||
- [Tests] `stringify`: increase coverage | ||
- [Tests] use `mock-property` | ||
- [Tests] `stringify`: improve coverage | ||
- [Dev Deps] update `@ljharb/eslint-config `, `aud`, `has-override-mistake`, `has-property-descriptors`, `mock-property`, `npmignore`, `object-inspect`, `tape` | ||
- [Dev Deps] pin `glob`, since v10.3.8+ requires a broken `jackspeak` | ||
- [Dev Deps] pin `jackspeak` since 2.1.2+ depends on npm aliases, which kill the install process in npm < 6 | ||
## **6.11.2** | ||
@@ -2,0 +25,0 @@ - [Fix] `parse`: Fix parsing when the global Object prototype is frozen (#473) |
@@ -10,2 +10,3 @@ 'use strict'; | ||
allowDots: false, | ||
allowEmptyArrays: false, | ||
allowPrototypes: false, | ||
@@ -17,5 +18,7 @@ allowSparse: false, | ||
comma: false, | ||
decodeDotInKeys: true, | ||
decoder: utils.decode, | ||
delimiter: '&', | ||
depth: 5, | ||
duplicates: 'combine', | ||
ignoreQueryPrefix: false, | ||
@@ -108,5 +111,6 @@ interpretNumericEntities: false, | ||
if (has.call(obj, key)) { | ||
var existing = has.call(obj, key); | ||
if (existing && options.duplicates === 'combine') { | ||
obj[key] = utils.combine(obj[key], val); | ||
} else { | ||
} else if (!existing || options.duplicates === 'last') { | ||
obj[key] = val; | ||
@@ -127,13 +131,14 @@ } | ||
if (root === '[]' && options.parseArrays) { | ||
obj = [].concat(leaf); | ||
obj = options.allowEmptyArrays && leaf === '' ? [] : [].concat(leaf); | ||
} else { | ||
obj = options.plainObjects ? Object.create(null) : {}; | ||
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; | ||
var index = parseInt(cleanRoot, 10); | ||
if (!options.parseArrays && cleanRoot === '') { | ||
var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot; | ||
var index = parseInt(decodedRoot, 10); | ||
if (!options.parseArrays && decodedRoot === '') { | ||
obj = { 0: leaf }; | ||
} else if ( | ||
!isNaN(index) | ||
&& root !== cleanRoot | ||
&& String(index) === cleanRoot | ||
&& root !== decodedRoot | ||
&& String(index) === decodedRoot | ||
&& index >= 0 | ||
@@ -144,4 +149,4 @@ && (options.parseArrays && index <= options.arrayLimit) | ||
obj[index] = leaf; | ||
} else if (cleanRoot !== '__proto__') { | ||
obj[cleanRoot] = leaf; | ||
} else if (decodedRoot !== '__proto__') { | ||
obj[decodedRoot] = leaf; | ||
} | ||
@@ -215,3 +220,11 @@ } | ||
if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { | ||
if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') { | ||
throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided'); | ||
} | ||
if (typeof opts.decodeDotInKeys !== 'undefined' && typeof opts.decodeDotInKeys !== 'boolean') { | ||
throw new TypeError('`decodeDotInKeys` option can only be `true` or `false`, when provided'); | ||
} | ||
if (opts.decoder !== null && typeof opts.decoder !== 'undefined' && typeof opts.decoder !== 'function') { | ||
throw new TypeError('Decoder has to be a function.'); | ||
@@ -225,4 +238,13 @@ } | ||
var duplicates = typeof opts.duplicates === 'undefined' ? defaults.duplicates : opts.duplicates; | ||
if (duplicates !== 'combine' && duplicates !== 'first' && duplicates !== 'last') { | ||
throw new TypeError('The duplicates option must be either combine, first, or last'); | ||
} | ||
var allowDots = typeof opts.allowDots === 'undefined' ? opts.decodeDotInKeys === true ? true : defaults.allowDots : !!opts.allowDots; | ||
return { | ||
allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, | ||
allowDots: allowDots, | ||
allowEmptyArrays: typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, | ||
allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes, | ||
@@ -234,2 +256,3 @@ allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse, | ||
comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma, | ||
decodeDotInKeys: typeof opts.decodeDotInKeys === 'boolean' ? opts.decodeDotInKeys : defaults.decodeDotInKeys, | ||
decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder, | ||
@@ -239,2 +262,3 @@ delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter, | ||
depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth, | ||
duplicates: duplicates, | ||
ignoreQueryPrefix: opts.ignoreQueryPrefix === true, | ||
@@ -241,0 +265,0 @@ interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities, |
@@ -33,2 +33,4 @@ 'use strict'; | ||
allowDots: false, | ||
allowEmptyArrays: false, | ||
arrayFormat: 'indices', | ||
charset: 'utf-8', | ||
@@ -38,2 +40,3 @@ charsetSentinel: false, | ||
encode: true, | ||
encodeDotInKeys: false, | ||
encoder: utils.encode, | ||
@@ -67,4 +70,6 @@ encodeValuesOnly: false, | ||
commaRoundTrip, | ||
allowEmptyArrays, | ||
strictNullHandling, | ||
skipNulls, | ||
encodeDotInKeys, | ||
encoder, | ||
@@ -151,4 +156,10 @@ filter, | ||
var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; | ||
var encodedPrefix = encodeDotInKeys ? prefix.replace(/\./g, '%2E') : prefix; | ||
var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? encodedPrefix + '[]' : encodedPrefix; | ||
if (allowEmptyArrays && isArray(obj) && obj.length === 0) { | ||
return adjustedPrefix + '[]'; | ||
} | ||
for (var j = 0; j < objKeys.length; ++j) { | ||
@@ -162,5 +173,6 @@ var key = objKeys[j]; | ||
var encodedKey = allowDots && encodeDotInKeys ? key.replace(/\./g, '%2E') : key; | ||
var keyPrefix = isArray(obj) | ||
? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, key) : adjustedPrefix | ||
: adjustedPrefix + (allowDots ? '.' + key : '[' + key + ']'); | ||
? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, encodedKey) : adjustedPrefix | ||
: adjustedPrefix + (allowDots ? '.' + encodedKey : '[' + encodedKey + ']'); | ||
@@ -175,4 +187,6 @@ sideChannel.set(object, step); | ||
commaRoundTrip, | ||
allowEmptyArrays, | ||
strictNullHandling, | ||
skipNulls, | ||
encodeDotInKeys, | ||
generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder, | ||
@@ -199,2 +213,10 @@ filter, | ||
if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') { | ||
throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided'); | ||
} | ||
if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') { | ||
throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided'); | ||
} | ||
if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') { | ||
@@ -223,9 +245,28 @@ throw new TypeError('Encoder has to be a function.'); | ||
var arrayFormat; | ||
if (opts.arrayFormat in arrayPrefixGenerators) { | ||
arrayFormat = opts.arrayFormat; | ||
} else if ('indices' in opts) { | ||
arrayFormat = opts.indices ? 'indices' : 'repeat'; | ||
} else { | ||
arrayFormat = defaults.arrayFormat; | ||
} | ||
if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { | ||
throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); | ||
} | ||
var allowDots = typeof opts.allowDots === 'undefined' ? opts.encodeDotInKeys === true ? true : defaults.allowDots : !!opts.allowDots; | ||
return { | ||
addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, | ||
allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, | ||
allowDots: allowDots, | ||
allowEmptyArrays: typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, | ||
arrayFormat: arrayFormat, | ||
charset: charset, | ||
charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, | ||
commaRoundTrip: opts.commaRoundTrip, | ||
delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, | ||
encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, | ||
encodeDotInKeys: typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys, | ||
encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, | ||
@@ -264,17 +305,5 @@ encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, | ||
var arrayFormat; | ||
if (opts && opts.arrayFormat in arrayPrefixGenerators) { | ||
arrayFormat = opts.arrayFormat; | ||
} else if (opts && 'indices' in opts) { | ||
arrayFormat = opts.indices ? 'indices' : 'repeat'; | ||
} else { | ||
arrayFormat = 'indices'; | ||
} | ||
var generateArrayPrefix = arrayPrefixGenerators[options.arrayFormat]; | ||
var commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip; | ||
var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; | ||
if (opts && 'commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { | ||
throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); | ||
} | ||
var commaRoundTrip = generateArrayPrefix === 'comma' && opts && opts.commaRoundTrip; | ||
if (!objKeys) { | ||
@@ -300,4 +329,6 @@ objKeys = Object.keys(obj); | ||
commaRoundTrip, | ||
options.allowEmptyArrays, | ||
options.strictNullHandling, | ||
options.skipNulls, | ||
options.encodeDotInKeys, | ||
options.encode ? options.encoder : null, | ||
@@ -304,0 +335,0 @@ options.filter, |
@@ -5,3 +5,3 @@ { | ||
"homepage": "https://github.com/ljharb/qs", | ||
"version": "6.11.2", | ||
"version": "6.12.0", | ||
"repository": { | ||
@@ -15,2 +15,3 @@ "type": "git", | ||
"main": "lib/index.js", | ||
"sideEffects": false, | ||
"contributors": [ | ||
@@ -35,30 +36,39 @@ { | ||
"dependencies": { | ||
"side-channel": "^1.0.4" | ||
"side-channel": "^1.0.6" | ||
}, | ||
"devDependencies": { | ||
"@ljharb/eslint-config": "^21.0.1", | ||
"aud": "^2.0.2", | ||
"@browserify/envify": "^6.0.0", | ||
"@browserify/uglifyify": "^6.0.0", | ||
"@ljharb/eslint-config": "^21.1.0", | ||
"aud": "^2.0.4", | ||
"browserify": "^16.5.2", | ||
"bundle-collapser": "^1.4.0", | ||
"common-shakeify": "~1.0.0", | ||
"eclint": "^2.8.1", | ||
"es-value-fixtures": "^1.4.2", | ||
"eslint": "=8.8.0", | ||
"evalmd": "^0.0.19", | ||
"for-each": "^0.3.3", | ||
"has-override-mistake": "^1.0.0", | ||
"has-property-descriptors": "^1.0.0", | ||
"glob": "=10.3.7", | ||
"has-override-mistake": "^1.0.1", | ||
"has-property-descriptors": "^1.0.2", | ||
"has-symbols": "^1.0.3", | ||
"iconv-lite": "^0.5.1", | ||
"in-publish": "^2.0.1", | ||
"jackspeak": "=2.1.1", | ||
"mkdirp": "^0.5.5", | ||
"mock-property": "^1.0.0", | ||
"npmignore": "^0.3.0", | ||
"mock-property": "^1.0.3", | ||
"module-deps": "^6.2.3", | ||
"npmignore": "^0.3.1", | ||
"nyc": "^10.3.2", | ||
"object-inspect": "^1.12.3", | ||
"object-inspect": "^1.13.1", | ||
"qs-iconv": "^1.0.4", | ||
"safe-publish-latest": "^2.0.0", | ||
"safer-buffer": "^2.1.2", | ||
"tape": "^5.6.3" | ||
"tape": "^5.7.5", | ||
"unassertify": "^3.0.1" | ||
}, | ||
"scripts": { | ||
"prepack": "npmignore --auto --commentLines=autogenerated", | ||
"prepublishOnly": "safe-publish-latest && npm run dist", | ||
"prepack": "npmignore --auto --commentLines=autogenerated && npm run dist", | ||
"prepublishOnly": "safe-publish-latest", | ||
"prepublish": "not-in-publish || npm run prepublishOnly", | ||
@@ -72,3 +82,3 @@ "pretest": "npm run --silent readme && npm run --silent lint", | ||
"lint": "eslint --ext=js,mjs .", | ||
"dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js" | ||
"dist": "mkdirp dist && browserify --standalone Qs -g unassertify -g @browserify/envify -g [@browserify/uglifyify --mangle.keep_fnames --compress.keep_fnames --format.indent_level=1 --compress.arrows=false --compress.passes=4 --compress.typeofs=false] -p common-shakeify -p bundle-collapser/plugin lib/index.js > dist/qs.js" | ||
}, | ||
@@ -81,5 +91,6 @@ "license": "BSD-3-Clause", | ||
"component.json", | ||
".github/workflows" | ||
".github/workflows", | ||
"logos" | ||
] | ||
} | ||
} |
134
README.md
@@ -0,1 +1,5 @@ | ||
<p align="center"> | ||
<img alt="qs" src="./logos/banner_default.png" width="800" /> | ||
</p> | ||
# qs <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
@@ -5,4 +9,2 @@ | ||
[![coverage][codecov-image]][codecov-url] | ||
[![dependency status][deps-svg]][deps-url] | ||
[![dev dependency status][dev-deps-svg]][dev-deps-url] | ||
[![License][license-image]][license-url] | ||
@@ -57,3 +59,5 @@ [![Downloads][downloads-image]][downloads-url] | ||
By default parameters that would overwrite properties on the object prototype are ignored, if you wish to keep the data from those fields either use `plainObjects` as mentioned above, or set `allowPrototypes` to `true` which will allow user input to overwrite those properties. *WARNING* It is generally a bad idea to enable this option as it can cause problems when attempting to use the properties that have been overwritten. Always be careful with this option. | ||
By default parameters that would overwrite properties on the object prototype are ignored, if you wish to keep the data from those fields either use `plainObjects` as mentioned above, or set `allowPrototypes` to `true` which will allow user input to overwrite those properties. | ||
*WARNING* It is generally a bad idea to enable this option as it can cause problems when attempting to use the properties that have been overwritten. | ||
Always be careful with this option. | ||
@@ -85,4 +89,4 @@ ```javascript | ||
By default, when nesting objects **qs** will only parse up to 5 children deep. This means if you attempt to parse a string like | ||
`'a[b][c][d][e][f][g][h][i]=j'` your resulting object will be: | ||
By default, when nesting objects **qs** will only parse up to 5 children deep. | ||
This means if you attempt to parse a string like `'a[b][c][d][e][f][g][h][i]=j'` your resulting object will be: | ||
@@ -153,6 +157,27 @@ ```javascript | ||
If you have to deal with legacy browsers or services, there's | ||
also support for decoding percent-encoded octets as iso-8859-1: | ||
Option `decodeDotInKeys` can be used to decode dots in keys | ||
Note: it implies `allowDots`, so `parse` will error if you set `decodeDotInKeys` to `true`, and `allowDots` to `false`. | ||
```javascript | ||
var withDots = qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { decodeDotInKeys: true }); | ||
assert.deepEqual(withDots, { 'name.obj': { first: 'John', last: 'Doe' }}); | ||
``` | ||
Option `allowEmptyArrays` can be used to allowing empty array values in object | ||
```javascript | ||
var withEmptyArrays = qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }); | ||
assert.deepEqual(withEmptyArrays, { foo: [], bar: 'baz' }); | ||
``` | ||
Option `duplicates` can be used to change the behavior when duplicate keys are encountered | ||
```javascript | ||
assert.deepEqual(qs.parse('foo=bar&foo=baz'), { foo: ['bar', 'baz'] }); | ||
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), { foo: ['bar', 'baz'] }); | ||
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), { foo: 'bar' }); | ||
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), { foo: 'baz' }); | ||
``` | ||
If you have to deal with legacy browsers or services, there's also support for decoding percent-encoded octets as iso-8859-1: | ||
```javascript | ||
var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' }); | ||
@@ -162,20 +187,11 @@ assert.deepEqual(oldCharset, { a: '§' }); | ||
Some services add an initial `utf8=✓` value to forms so that old | ||
Internet Explorer versions are more likely to submit the form as | ||
utf-8. Additionally, the server can check the value against wrong | ||
encodings of the checkmark character and detect that a query string | ||
or `application/x-www-form-urlencoded` body was *not* sent as | ||
utf-8, eg. if the form had an `accept-charset` parameter or the | ||
containing page had a different character set. | ||
Some services add an initial `utf8=✓` value to forms so that old Internet Explorer versions are more likely to submit the form as utf-8. | ||
Additionally, the server can check the value against wrong encodings of the checkmark character and detect that a query string or `application/x-www-form-urlencoded` body was *not* sent as utf-8, eg. if the form had an `accept-charset` parameter or the containing page had a different character set. | ||
**qs** supports this mechanism via the `charsetSentinel` option. | ||
If specified, the `utf8` parameter will be omitted from the | ||
returned object. It will be used to switch to `iso-8859-1`/`utf-8` | ||
mode depending on how the checkmark is encoded. | ||
If specified, the `utf8` parameter will be omitted from the returned object. | ||
It will be used to switch to `iso-8859-1`/`utf-8` mode depending on how the checkmark is encoded. | ||
**Important**: When you specify both the `charset` option and the | ||
`charsetSentinel` option, the `charset` will be overridden when | ||
the request contains a `utf8` parameter from which the actual | ||
charset can be deduced. In that sense the `charset` will behave | ||
as the default charset rather than the authoritative charset. | ||
**Important**: When you specify both the `charset` option and the `charsetSentinel` option, the `charset` will be overridden when the request contains a `utf8` parameter from which the actual charset can be deduced. | ||
In that sense the `charset` will behave as the default charset rather than the authoritative charset. | ||
@@ -197,4 +213,3 @@ ```javascript | ||
If you want to decode the `&#...;` syntax to the actual character, | ||
you can specify the `interpretNumericEntities` option as well: | ||
If you want to decode the `&#...;` syntax to the actual character, you can specify the `interpretNumericEntities` option as well: | ||
@@ -209,4 +224,3 @@ ```javascript | ||
It also works when the charset has been detected in `charsetSentinel` | ||
mode. | ||
It also works when the charset has been detected in `charsetSentinel` mode. | ||
@@ -229,5 +243,4 @@ ### Parsing Arrays | ||
Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number | ||
to create an array. When creating arrays with specific indices, **qs** will compact a sparse array to only the existing values preserving | ||
their order: | ||
Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number to create an array. | ||
When creating arrays with specific indices, **qs** will compact a sparse array to only the existing values preserving their order: | ||
@@ -256,4 +269,5 @@ ```javascript | ||
**qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will | ||
instead be converted to an object with the index as the key. This is needed to handle cases when someone sent, for example, `a[999999999]` and it will take significant time to iterate over this huge array. | ||
**qs** will also limit specifying indices in an array to a maximum index of `20`. | ||
Any array members with an index of greater than `20` will instead be converted to an object with the index as the key. | ||
This is needed to handle cases when someone sent, for example, `a[999999999]` and it will take significant time to iterate over this huge array. | ||
@@ -302,3 +316,4 @@ ```javascript | ||
By default, all values are parsed as strings. This behavior will not change and is explained in [issue #91](https://github.com/ljharb/qs/issues/91). | ||
By default, all values are parsed as strings. | ||
This behavior will not change and is explained in [issue #91](https://github.com/ljharb/qs/issues/91). | ||
@@ -386,5 +401,6 @@ ```javascript | ||
Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage. | ||
Examples beyond this point will be shown as though the output is not URI encoded for clarity. | ||
Please note that the return values in these cases *will* be URI encoded during real usage. | ||
When arrays are stringified, by default they are given explicit indices: | ||
When arrays are stringified, they follow the `arrayFormat` option, which defaults to `indices`: | ||
@@ -396,3 +412,3 @@ ```javascript | ||
You may override this by setting the `indices` option to `false`: | ||
You may override this by setting the `indices` option to `false`, or to be more explicit, the `arrayFormat` option to `repeat`: | ||
@@ -433,2 +449,16 @@ ```javascript | ||
You may encode the dot notation in the keys of object with option `encodeDotInKeys` by setting it to `true`: | ||
Note: it implies `allowDots`, so `stringify` will error if you set `decodeDotInKeys` to `true`, and `allowDots` to `false`. | ||
Caveat: when `encodeValuesOnly` is `true` as well as `encodeDotInKeys`, only dots in keys and nothing else will be encoded. | ||
```javascript | ||
qs.stringify({ "name.obj": { "first": "John", "last": "Doe" } }, { allowDots: true, encodeDotInKeys: true }) | ||
// 'name%252Eobj.first=John&name%252Eobj.last=Doe' | ||
``` | ||
You may allow empty array values by setting the `allowEmptyArrays` option to `true`: | ||
```javascript | ||
qs.stringify({ foo: [], bar: 'baz' }, { allowEmptyArrays: true }); | ||
// 'foo[]&bar=baz' | ||
``` | ||
Empty strings and null values will omit the value, but the equals sign (=) remains in place: | ||
@@ -489,4 +519,4 @@ | ||
Finally, you can use the `filter` option to restrict which keys will be included in the stringified output. | ||
If you pass a function, it will be called for each key to obtain the replacement value. Otherwise, if you | ||
pass an array, it will be used to select properties and array indices for stringification: | ||
If you pass a function, it will be called for each key to obtain the replacement value. | ||
Otherwise, if you pass an array, it will be used to select properties and array indices for stringification: | ||
@@ -515,4 +545,4 @@ ```javascript | ||
You could also use `filter` to inject custom serialization for user defined types. Consider you're working with | ||
some api that expects query strings of the format for ranges: | ||
You could also use `filter` to inject custom serialization for user defined types. | ||
Consider you're working with some api that expects query strings of the format for ranges: | ||
@@ -563,3 +593,4 @@ ``` | ||
Parsing does not distinguish between parameters with and without equal signs. Both are converted to empty strings. | ||
Parsing does not distinguish between parameters with and without equal signs. | ||
Both are converted to empty strings. | ||
@@ -593,4 +624,3 @@ ```javascript | ||
If you're communicating with legacy systems, you can switch to `iso-8859-1` | ||
using the `charset` option: | ||
If you're communicating with legacy systems, you can switch to `iso-8859-1` using the `charset` option: | ||
@@ -602,4 +632,3 @@ ```javascript | ||
Characters that don't exist in `iso-8859-1` will be converted to numeric | ||
entities, similar to what browsers do: | ||
Characters that don't exist in `iso-8859-1` will be converted to numeric entities, similar to what browsers do: | ||
@@ -611,5 +640,3 @@ ```javascript | ||
You can use the `charsetSentinel` option to announce the character by | ||
including an `utf8=✓` parameter with the proper encoding if the checkmark, | ||
similar to what Ruby on Rails and others do when submitting forms. | ||
You can use the `charsetSentinel` option to announce the character by including an `utf8=✓` parameter with the proper encoding if the checkmark, similar to what Ruby on Rails and others do when submitting forms. | ||
@@ -626,4 +653,3 @@ ```javascript | ||
By default the encoding and decoding of characters is done in `utf-8`, | ||
and `iso-8859-1` support is also built in via the `charset` parameter. | ||
By default the encoding and decoding of characters is done in `utf-8`, and `iso-8859-1` support is also built in via the `charset` parameter. | ||
@@ -667,3 +693,5 @@ If you wish to encode querystrings to a different character set (i.e. | ||
The maintainers of qs and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-qs?utm_source=npm-qs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) | ||
The maintainers of qs and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. | ||
Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. | ||
[Learn more.](https://tidelift.com/subscription/pkg/npm-qs?utm_source=npm-qs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) | ||
@@ -685,1 +713,7 @@ [package-url]: https://npmjs.org/package/qs | ||
[actions-url]: https://github.com/ljharb/qs/actions | ||
## Acknowledgements | ||
qs logo by [NUMI](https://github.com/numi-hq/open-design): | ||
[<img src="https://raw.githubusercontent.com/numi-hq/open-design/main/assets/numi-lockup.png" alt="NUMI Logo" style="width: 200px;"/>](https://numi.tech/?ref=qs) |
@@ -5,34 +5,264 @@ 'use strict'; | ||
emptyTestCases: [ | ||
{ input: '&', withEmptyKeys: {}, stringifyOutput: '', noEmptyKeys: {} }, | ||
{ input: '&&', withEmptyKeys: {}, stringifyOutput: '', noEmptyKeys: {} }, | ||
{ input: '&=', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} }, | ||
{ input: '&=&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} }, | ||
{ input: '&=&=', withEmptyKeys: { '': ['', ''] }, stringifyOutput: '[0]=&[1]=', noEmptyKeys: {} }, | ||
{ input: '&=&=&', withEmptyKeys: { '': ['', ''] }, stringifyOutput: '[0]=&[1]=', noEmptyKeys: {} }, | ||
{ input: '=', withEmptyKeys: { '': '' }, noEmptyKeys: {}, stringifyOutput: '=' }, | ||
{ input: '=&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} }, | ||
{ input: '=&&&', withEmptyKeys: { '': '' }, stringifyOutput: '=', noEmptyKeys: {} }, | ||
{ input: '=&=&=&', withEmptyKeys: { '': ['', '', ''] }, stringifyOutput: '[0]=&[1]=&[2]=', noEmptyKeys: {} }, | ||
{ input: '=&a[]=b&a[1]=c', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: '=a', withEmptyKeys: { '': 'a' }, noEmptyKeys: {}, stringifyOutput: '=a' }, | ||
{ input: '=a', withEmptyKeys: { '': 'a' }, noEmptyKeys: {}, stringifyOutput: '=a' }, | ||
{ input: 'a==a', withEmptyKeys: { a: '=a' }, noEmptyKeys: { a: '=a' }, stringifyOutput: 'a==a' }, | ||
{ input: '=&a[]=b', withEmptyKeys: { '': '', a: ['b'] }, stringifyOutput: '=&a[0]=b', noEmptyKeys: { a: ['b'] } }, | ||
{ input: '=&a[]=b&a[]=c&a[2]=d', withEmptyKeys: { '': '', a: ['b', 'c', 'd'] }, stringifyOutput: '=&a[0]=b&a[1]=c&a[2]=d', noEmptyKeys: { a: ['b', 'c', 'd'] } }, | ||
{ input: '=a&=b', withEmptyKeys: { '': ['a', 'b'] }, stringifyOutput: '[0]=a&[1]=b', noEmptyKeys: {} }, | ||
{ input: '=a&foo=b', withEmptyKeys: { '': 'a', foo: 'b' }, noEmptyKeys: { foo: 'b' }, stringifyOutput: '=a&foo=b' }, | ||
{ input: 'a[]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: 'a[]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: 'a[0]=b&a=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: 'a=b&a[]=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: 'a=b&a[0]=c&=', withEmptyKeys: { '': '', a: ['b', 'c'] }, stringifyOutput: '=&a[0]=b&a[1]=c', noEmptyKeys: { a: ['b', 'c'] } }, | ||
{ input: '[]=a&[]=b& []=1', withEmptyKeys: { '': ['a', 'b'], ' ': ['1'] }, stringifyOutput: '[0]=a&[1]=b& [0]=1', noEmptyKeys: { 0: 'a', 1: 'b', ' ': ['1'] } }, | ||
{ input: '[0]=a&[1]=b&a[0]=1&a[1]=2', withEmptyKeys: { '': ['a', 'b'], a: ['1', '2'] }, noEmptyKeys: { 0: 'a', 1: 'b', a: ['1', '2'] }, stringifyOutput: '[0]=a&[1]=b&a[0]=1&a[1]=2' }, | ||
{ input: '[deep]=a&[deep]=2', withEmptyKeys: { '': { deep: ['a', '2'] } }, stringifyOutput: '[deep][0]=a&[deep][1]=2', noEmptyKeys: { deep: ['a', '2'] } }, | ||
{ input: '%5B0%5D=a&%5B1%5D=b', withEmptyKeys: { '': ['a', 'b'] }, stringifyOutput: '[0]=a&[1]=b', noEmptyKeys: { 0: 'a', 1: 'b' } } | ||
{ | ||
input: '&', | ||
withEmptyKeys: {}, | ||
stringifyOutput: { | ||
brackets: '', | ||
indices: '', | ||
repeat: '' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '&&', | ||
withEmptyKeys: {}, | ||
stringifyOutput: { | ||
brackets: '', | ||
indices: '', | ||
repeat: '' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '&=', | ||
withEmptyKeys: { '': '' }, | ||
stringifyOutput: { | ||
brackets: '=', | ||
indices: '=', | ||
repeat: '=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '&=&', | ||
withEmptyKeys: { '': '' }, | ||
stringifyOutput: { | ||
brackets: '=', | ||
indices: '=', | ||
repeat: '=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '&=&=', | ||
withEmptyKeys: { '': ['', ''] }, | ||
stringifyOutput: { | ||
brackets: '[]=&[]=', | ||
indices: '[0]=&[1]=', | ||
repeat: '=&=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '&=&=&', | ||
withEmptyKeys: { '': ['', ''] }, | ||
stringifyOutput: { | ||
brackets: '[]=&[]=', | ||
indices: '[0]=&[1]=', | ||
repeat: '=&=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '=', | ||
withEmptyKeys: { '': '' }, | ||
noEmptyKeys: {}, | ||
stringifyOutput: { | ||
brackets: '=', | ||
indices: '=', | ||
repeat: '=' | ||
} | ||
}, | ||
{ | ||
input: '=&', | ||
withEmptyKeys: { '': '' }, | ||
stringifyOutput: { | ||
brackets: '=', | ||
indices: '=', | ||
repeat: '=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '=&&&', | ||
withEmptyKeys: { '': '' }, | ||
stringifyOutput: { | ||
brackets: '=', | ||
indices: '=', | ||
repeat: '=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '=&=&=&', | ||
withEmptyKeys: { '': ['', '', ''] }, | ||
stringifyOutput: { | ||
brackets: '[]=&[]=&[]=', | ||
indices: '[0]=&[1]=&[2]=', | ||
repeat: '=&=&=' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '=&a[]=b&a[1]=c', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: '=a', | ||
withEmptyKeys: { '': 'a' }, | ||
noEmptyKeys: {}, | ||
stringifyOutput: { | ||
brackets: '=a', | ||
indices: '=a', | ||
repeat: '=a' | ||
} | ||
}, | ||
{ | ||
input: 'a==a', | ||
withEmptyKeys: { a: '=a' }, | ||
noEmptyKeys: { a: '=a' }, | ||
stringifyOutput: { | ||
brackets: 'a==a', | ||
indices: 'a==a', | ||
repeat: 'a==a' | ||
} | ||
}, | ||
{ | ||
input: '=&a[]=b', | ||
withEmptyKeys: { '': '', a: ['b'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b', | ||
indices: '=&a[0]=b', | ||
repeat: '=&a=b' | ||
}, | ||
noEmptyKeys: { a: ['b'] } | ||
}, | ||
{ | ||
input: '=&a[]=b&a[]=c&a[2]=d', | ||
withEmptyKeys: { '': '', a: ['b', 'c', 'd'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c&a[]=d', | ||
indices: '=&a[0]=b&a[1]=c&a[2]=d', | ||
repeat: '=&a=b&a=c&a=d' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c', 'd'] } | ||
}, | ||
{ | ||
input: '=a&=b', | ||
withEmptyKeys: { '': ['a', 'b'] }, | ||
stringifyOutput: { | ||
brackets: '[]=a&[]=b', | ||
indices: '[0]=a&[1]=b', | ||
repeat: '=a&=b' | ||
}, | ||
noEmptyKeys: {} | ||
}, | ||
{ | ||
input: '=a&foo=b', | ||
withEmptyKeys: { '': 'a', foo: 'b' }, | ||
noEmptyKeys: { foo: 'b' }, | ||
stringifyOutput: { | ||
brackets: '=a&foo=b', | ||
indices: '=a&foo=b', | ||
repeat: '=a&foo=b' | ||
} | ||
}, | ||
{ | ||
input: 'a[]=b&a=c&=', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: 'a[]=b&a=c&=', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: 'a[0]=b&a=c&=', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: 'a=b&a[]=c&=', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: 'a=b&a[0]=c&=', | ||
withEmptyKeys: { '': '', a: ['b', 'c'] }, | ||
stringifyOutput: { | ||
brackets: '=&a[]=b&a[]=c', | ||
indices: '=&a[0]=b&a[1]=c', | ||
repeat: '=&a=b&a=c' | ||
}, | ||
noEmptyKeys: { a: ['b', 'c'] } | ||
}, | ||
{ | ||
input: '[]=a&[]=b& []=1', | ||
withEmptyKeys: { '': ['a', 'b'], ' ': ['1'] }, | ||
stringifyOutput: { | ||
brackets: '[]=a&[]=b& []=1', | ||
indices: '[0]=a&[1]=b& [0]=1', | ||
repeat: '=a&=b& =1' | ||
}, | ||
noEmptyKeys: { 0: 'a', 1: 'b', ' ': ['1'] } | ||
}, | ||
{ | ||
input: '[0]=a&[1]=b&a[0]=1&a[1]=2', | ||
withEmptyKeys: { '': ['a', 'b'], a: ['1', '2'] }, | ||
noEmptyKeys: { 0: 'a', 1: 'b', a: ['1', '2'] }, | ||
stringifyOutput: { | ||
brackets: '[]=a&[]=b&a[]=1&a[]=2', | ||
indices: '[0]=a&[1]=b&a[0]=1&a[1]=2', | ||
repeat: '=a&=b&a=1&a=2' | ||
} | ||
}, | ||
{ | ||
input: '[deep]=a&[deep]=2', | ||
withEmptyKeys: { '': { deep: ['a', '2'] } | ||
}, | ||
stringifyOutput: { | ||
brackets: '[deep][]=a&[deep][]=2', | ||
indices: '[deep][0]=a&[deep][1]=2', | ||
repeat: '[deep]=a&[deep]=2' | ||
}, | ||
noEmptyKeys: { deep: ['a', '2'] } | ||
}, | ||
{ | ||
input: '%5B0%5D=a&%5B1%5D=b', | ||
withEmptyKeys: { '': ['a', 'b'] }, | ||
stringifyOutput: { | ||
brackets: '[]=a&[]=b', | ||
indices: '[0]=a&[1]=b', | ||
repeat: '=a&=b' | ||
}, | ||
noEmptyKeys: { 0: 'a', 1: 'b' } | ||
} | ||
] | ||
}; |
@@ -9,2 +9,4 @@ 'use strict'; | ||
var SaferBuffer = require('safer-buffer').Buffer; | ||
var v = require('es-value-fixtures'); | ||
var inspect = require('object-inspect'); | ||
var emptyTestCases = require('./empty-keys-cases').emptyTestCases; | ||
@@ -41,40 +43,141 @@ | ||
t.test('arrayFormat: brackets allows only explicit arrays', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'brackets' }), { a: 'b,c' }); | ||
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'brackets' }), { a: ['b', 'c'] }); | ||
t.test('comma: false', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c'), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c'), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c'), { a: 'b,c' }); | ||
st.deepEqual(qs.parse('a=b&a=c'), { a: ['b', 'c'] }); | ||
st.end(); | ||
}); | ||
t.test('arrayFormat: indices allows only indexed arrays', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'indices' }), { a: 'b,c' }); | ||
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'indices' }), { a: ['b', 'c'] }); | ||
t.test('comma: true', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c', { comma: true }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { comma: true }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c', { comma: true }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b&a=c', { comma: true }), { a: ['b', 'c'] }); | ||
st.end(); | ||
}); | ||
t.test('arrayFormat: comma allows only comma-separated arrays', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'comma' }), { a: 'b,c' }); | ||
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'comma' }), { a: ['b', 'c'] }); | ||
t.test('allows enabling dot notation', function (st) { | ||
st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' }); | ||
st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } }); | ||
st.end(); | ||
}); | ||
t.test('arrayFormat: repeat allows only repeated values', function (st) { | ||
st.deepEqual(qs.parse('a[]=b&a[]=c', { arrayFormat: 'repeat' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayFormat: 'repeat' }), { a: ['b', 'c'] }); | ||
st.deepEqual(qs.parse('a=b,c', { arrayFormat: 'repeat' }), { a: 'b,c' }); | ||
st.deepEqual(qs.parse('a=b&a=c', { arrayFormat: 'repeat' }), { a: ['b', 'c'] }); | ||
t.test('decode dot keys correctly', function (st) { | ||
st.deepEqual( | ||
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: false, decodeDotInKeys: false }), | ||
{ 'name%2Eobj.first': 'John', 'name%2Eobj.last': 'Doe' }, | ||
'with allowDots false and decodeDotInKeys false' | ||
); | ||
st.deepEqual( | ||
qs.parse('name.obj.first=John&name.obj.last=Doe', { allowDots: true, decodeDotInKeys: false }), | ||
{ name: { obj: { first: 'John', last: 'Doe' } } }, | ||
'with allowDots false and decodeDotInKeys false' | ||
); | ||
st.deepEqual( | ||
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: false }), | ||
{ 'name%2Eobj': { first: 'John', last: 'Doe' } }, | ||
'with allowDots true and decodeDotInKeys false' | ||
); | ||
st.deepEqual( | ||
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: true, decodeDotInKeys: true }), | ||
{ 'name.obj': { first: 'John', last: 'Doe' } }, | ||
'with allowDots true and decodeDotInKeys true' | ||
); | ||
st.deepEqual( | ||
qs.parse( | ||
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', | ||
{ allowDots: false, decodeDotInKeys: false } | ||
), | ||
{ 'name%2Eobj%2Esubobject.first%2Egodly%2Ename': 'John', 'name%2Eobj%2Esubobject.last': 'Doe' }, | ||
'with allowDots false and decodeDotInKeys false' | ||
); | ||
st.deepEqual( | ||
qs.parse( | ||
'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe', | ||
{ allowDots: true, decodeDotInKeys: false } | ||
), | ||
{ name: { obj: { subobject: { first: { godly: { name: 'John' } }, last: 'Doe' } } } }, | ||
'with allowDots true and decodeDotInKeys false' | ||
); | ||
st.deepEqual( | ||
qs.parse( | ||
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', | ||
{ allowDots: true, decodeDotInKeys: true } | ||
), | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
'with allowDots true and decodeDotInKeys true' | ||
); | ||
st.end(); | ||
}); | ||
t.test('allows enabling dot notation', function (st) { | ||
st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' }); | ||
st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } }); | ||
t.test('should decode dot in key of object, and allow enabling dot notation when decodeDotInKeys is set to true and allowDots is undefined', function (st) { | ||
st.deepEqual( | ||
qs.parse( | ||
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', | ||
{ decodeDotInKeys: true } | ||
), | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
'with allowDots undefined and decodeDotInKeys true' | ||
); | ||
st.end(); | ||
}); | ||
t.test('should throw when decodeDotInKeys is not of type boolean', function (st) { | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 'foobar' }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: 0 }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: NaN }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { decodeDotInKeys: null }); }, | ||
TypeError | ||
); | ||
st.end(); | ||
}); | ||
t.test('allows empty arrays in obj values', function (st) { | ||
st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }), { foo: [], bar: 'baz' }); | ||
st.deepEqual(qs.parse('foo[]&bar=baz', { allowEmptyArrays: false }), { foo: [''], bar: 'baz' }); | ||
st.end(); | ||
}); | ||
t.test('should throw when allowEmptyArrays is not of type boolean', function (st) { | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 'foobar' }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: 0 }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: NaN }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.parse('foo[]&bar=baz', { allowEmptyArrays: null }); }, | ||
TypeError | ||
); | ||
st.end(); | ||
}); | ||
t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single nested string'); | ||
@@ -332,4 +435,5 @@ t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a double nested string'); | ||
t.test('should not throw when a native prototype has an enumerable property', function (st) { | ||
Object.prototype.crash = ''; | ||
Array.prototype.crash = ''; | ||
st.intercept(Object.prototype, 'crash', { value: '' }); | ||
st.intercept(Array.prototype, 'crash', { value: '' }); | ||
st.doesNotThrow(qs.parse.bind(null, 'a=b')); | ||
@@ -339,4 +443,3 @@ st.deepEqual(qs.parse('a=b'), { a: 'b' }); | ||
st.deepEqual(qs.parse('a[][b]=c'), { a: [{ b: 'c' }] }); | ||
delete Object.prototype.crash; | ||
delete Array.prototype.crash; | ||
st.end(); | ||
@@ -372,4 +475,10 @@ }); | ||
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } }); | ||
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: ['b'] }); | ||
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } }); | ||
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: 0 }), { a: { '-1': 'b' } }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: -1 }), { a: { 0: 'b', 1: 'c' } }); | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } }); | ||
st.end(); | ||
@@ -512,6 +621,8 @@ }); | ||
t.test('does not blow up when Buffer global is missing', function (st) { | ||
var tempBuffer = global.Buffer; | ||
delete global.Buffer; | ||
var restore = mockProperty(global, 'Buffer', { 'delete': true }); | ||
var result = qs.parse('a=b&c=d'); | ||
global.Buffer = tempBuffer; | ||
restore(); | ||
st.deepEqual(result, { a: 'b', c: 'd' }); | ||
@@ -905,1 +1016,39 @@ st.end(); | ||
}); | ||
test('`duplicates` option', function (t) { | ||
v.nonStrings.concat('not a valid option').forEach(function (invalidOption) { | ||
if (typeof invalidOption !== 'undefined') { | ||
t['throws']( | ||
function () { qs.parse('', { duplicates: invalidOption }); }, | ||
TypeError, | ||
'throws on invalid option: ' + inspect(invalidOption) | ||
); | ||
} | ||
}); | ||
t.deepEqual( | ||
qs.parse('foo=bar&foo=baz'), | ||
{ foo: ['bar', 'baz'] }, | ||
'duplicates: default, combine' | ||
); | ||
t.deepEqual( | ||
qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), | ||
{ foo: ['bar', 'baz'] }, | ||
'duplicates: combine' | ||
); | ||
t.deepEqual( | ||
qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), | ||
{ foo: 'bar' }, | ||
'duplicates: first' | ||
); | ||
t.deepEqual( | ||
qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), | ||
{ foo: 'baz' }, | ||
'duplicates: last' | ||
); | ||
t.end(); | ||
}); |
@@ -9,2 +9,3 @@ 'use strict'; | ||
var hasSymbols = require('has-symbols'); | ||
var mockProperty = require('mock-property'); | ||
var emptyTestCases = require('./empty-keys-cases').emptyTestCases; | ||
@@ -68,2 +69,124 @@ var hasBigInt = typeof BigInt === 'function'; | ||
t.test('encodes dot in key of object when encodeDotInKeys and allowDots is provided', function (st) { | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj': { first: 'John', last: 'Doe' } }, | ||
{ allowDots: false, encodeDotInKeys: false } | ||
), | ||
'name.obj%5Bfirst%5D=John&name.obj%5Blast%5D=Doe', | ||
'with allowDots false and encodeDotInKeys false' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj': { first: 'John', last: 'Doe' } }, | ||
{ allowDots: true, encodeDotInKeys: false } | ||
), | ||
'name.obj.first=John&name.obj.last=Doe', | ||
'with allowDots true and encodeDotInKeys false' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj': { first: 'John', last: 'Doe' } }, | ||
{ allowDots: false, encodeDotInKeys: true } | ||
), | ||
'name%252Eobj%5Bfirst%5D=John&name%252Eobj%5Blast%5D=Doe', | ||
'with allowDots false and encodeDotInKeys true' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj': { first: 'John', last: 'Doe' } }, | ||
{ allowDots: true, encodeDotInKeys: true } | ||
), | ||
'name%252Eobj.first=John&name%252Eobj.last=Doe', | ||
'with allowDots true and encodeDotInKeys true' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
{ allowDots: false, encodeDotInKeys: false } | ||
), | ||
'name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe', | ||
'with allowDots false and encodeDotInKeys false' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
{ allowDots: true, encodeDotInKeys: false } | ||
), | ||
'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe', | ||
'with allowDots false and encodeDotInKeys false' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
{ allowDots: false, encodeDotInKeys: true } | ||
), | ||
'name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe', | ||
'with allowDots false and encodeDotInKeys true' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
{ allowDots: true, encodeDotInKeys: true } | ||
), | ||
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', | ||
'with allowDots true and encodeDotInKeys true' | ||
); | ||
st.end(); | ||
}); | ||
t.test('should encode dot in key of object, and automatically set allowDots to `true` when encodeDotInKeys is true and allowDots in undefined', function (st) { | ||
st.equal( | ||
qs.stringify( | ||
{ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, | ||
{ encodeDotInKeys: true } | ||
), | ||
'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe', | ||
'with allowDots undefined and encodeDotInKeys true' | ||
); | ||
st.end(); | ||
}); | ||
t.test('should encode dot in key of object when encodeDotInKeys and allowDots is provided, and nothing else when encodeValuesOnly is provided', function (st) { | ||
st.equal( | ||
qs.stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, { | ||
encodeDotInKeys: true, allowDots: true, encodeValuesOnly: true | ||
}), | ||
'name%2Eobj.first=John&name%2Eobj.last=Doe' | ||
); | ||
st.equal( | ||
qs.stringify({ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }), | ||
'name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe' | ||
); | ||
st.end(); | ||
}); | ||
t.test('should throw when encodeDotInKeys is not of type boolean', function (st) { | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); }, | ||
TypeError | ||
); | ||
st.end(); | ||
}); | ||
t.test('adds query prefix', function (st) { | ||
@@ -92,3 +215,3 @@ st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); | ||
t.test('stringifies a nested object with dots notation', function (st) { | ||
t.test('`allowDots` option: stringifies a nested object with dots notation', function (st) { | ||
st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c'); | ||
@@ -116,2 +239,7 @@ st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e'); | ||
st.equal( | ||
qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true }), | ||
'a=b%2Cc%2Cd', | ||
'comma round trip => comma' | ||
); | ||
st.equal( | ||
qs.stringify({ a: ['b', 'c', 'd'] }), | ||
@@ -124,9 +252,15 @@ 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d', | ||
t.test('omits nulls when asked', function (st) { | ||
st.equal(qs.stringify({ a: 'b', c: null }, { skipNulls: true }), 'a=b'); | ||
st.end(); | ||
}); | ||
t.test('`skipNulls` option', function (st) { | ||
st.equal( | ||
qs.stringify({ a: 'b', c: null }, { skipNulls: true }), | ||
'a=b', | ||
'omits nulls when asked' | ||
); | ||
t.test('omits nested nulls when asked', function (st) { | ||
st.equal(qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), 'a%5Bb%5D=c'); | ||
st.equal( | ||
qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), | ||
'a%5Bb%5D=c', | ||
'omits nested nulls when asked' | ||
); | ||
st.end(); | ||
@@ -137,5 +271,44 @@ }); | ||
st.equal(qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d'); | ||
st.end(); | ||
}); | ||
t.test('omits object key/value pair when value is empty array', function (st) { | ||
st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz'); | ||
st.end(); | ||
}); | ||
t.test('should not omit object key/value pair when value is empty array and when asked', function (st) { | ||
st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz'); | ||
st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false }), 'b=zz'); | ||
st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true }), 'a[]&b=zz'); | ||
st.end(); | ||
}); | ||
t.test('should throw when allowEmptyArrays is not of type boolean', function (st) { | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); }, | ||
TypeError | ||
); | ||
st['throws']( | ||
function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); }, | ||
TypeError | ||
); | ||
st.end(); | ||
}); | ||
t.test('stringifies an array value with one item vs multiple items', function (st) { | ||
@@ -165,2 +338,3 @@ st.test('non-array item', function (s2t) { | ||
s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c,d'); | ||
s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c,d'); | ||
s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d'); | ||
@@ -175,2 +349,5 @@ | ||
s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd,e'); | ||
s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd%2Ce'); | ||
s2t.end(); | ||
@@ -266,14 +443,19 @@ }); | ||
st.equal( | ||
qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices' }), | ||
'a%5B0%5D%5Bb%5D=c', // a[0][b]=c | ||
'indices => brackets' | ||
qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), | ||
'a[0][b]=c', | ||
'indices => indices' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets' }), | ||
'a%5B%5D%5Bb%5D=c', // a[][b]=c | ||
qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), | ||
'a[b]=c', | ||
'repeat => repeat' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), | ||
'a[][b]=c', | ||
'brackets => brackets' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: 'c' }] }), | ||
'a%5B0%5D%5Bb%5D=c', | ||
qs.stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true }), | ||
'a[0][b]=c', | ||
'default => indices' | ||
@@ -283,16 +465,19 @@ ); | ||
st.equal( | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices' }), | ||
'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1', | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true }), | ||
'a[0][b][c][0]=1', | ||
'indices => indices' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets' }), | ||
'a%5B%5D%5Bb%5D%5Bc%5D%5B%5D=1', | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }), | ||
'a[b][c]=1', | ||
'repeat => repeat' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }), | ||
'a[][b][c][]=1', | ||
'brackets => brackets' | ||
); | ||
st.equal( | ||
qs.stringify({ a: [{ b: { c: [1] } }] }), | ||
'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1', | ||
qs.stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true }), | ||
'a[0][b][c][0]=1', | ||
'default => indices' | ||
@@ -399,3 +584,3 @@ ); | ||
t.test('uses indices notation for arrays when no arrayFormat=indices', function (st) { | ||
t.test('uses indices notation for arrays when arrayFormat=indices', function (st) { | ||
st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c'); | ||
@@ -405,3 +590,3 @@ st.end(); | ||
t.test('uses repeat notation for arrays when no arrayFormat=repeat', function (st) { | ||
t.test('uses repeat notation for arrays when arrayFormat=repeat', function (st) { | ||
st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c'); | ||
@@ -411,3 +596,3 @@ st.end(); | ||
t.test('uses brackets notation for arrays when no arrayFormat=brackets', function (st) { | ||
t.test('uses brackets notation for arrays when arrayFormat=brackets', function (st) { | ||
st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c'); | ||
@@ -509,6 +694,7 @@ st.end(); | ||
t.test('skips properties that are part of the object prototype', function (st) { | ||
Object.prototype.crash = 'test'; | ||
st.intercept(Object.prototype, 'crash', { value: 'test' }); | ||
st.equal(qs.stringify({ a: 'b' }), 'a=b'); | ||
st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); | ||
delete Object.prototype.crash; | ||
st.end(); | ||
@@ -537,6 +723,8 @@ }); | ||
t.test('does not blow up when Buffer global is missing', function (st) { | ||
var tempBuffer = global.Buffer; | ||
delete global.Buffer; | ||
var restore = mockProperty(global, 'Buffer', { 'delete': true }); | ||
var result = qs.stringify({ a: 'b', c: 'd' }); | ||
global.Buffer = tempBuffer; | ||
restore(); | ||
st.equal(result, 'a=b&c=d'); | ||
@@ -590,5 +778,13 @@ st.end(); | ||
st.equal( | ||
qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true }), | ||
qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), | ||
'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23' | ||
); | ||
st.equal( | ||
qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), | ||
'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23' | ||
); | ||
st.equal( | ||
qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), | ||
'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23' | ||
); | ||
@@ -707,3 +903,17 @@ st.end(); | ||
t.test('receives the default encoder as a second argument', function (st) { | ||
st.plan(8); | ||
qs.stringify({ a: 1, b: new Date(), c: true, d: [1] }, { | ||
encoder: function (str) { | ||
st.match(typeof str, /^(?:string|number|boolean)$/); | ||
return ''; | ||
} | ||
}); | ||
st.end(); | ||
}); | ||
t.test('receives the default encoder as a second argument', function (st) { | ||
st.plan(2); | ||
qs.stringify({ a: 1 }, { | ||
@@ -714,2 +924,3 @@ encoder: function (str, defaultEncoder) { | ||
}); | ||
st.end(); | ||
@@ -842,12 +1053,49 @@ }); | ||
{ a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, | ||
{ encodeValuesOnly: true } | ||
{ encodeValuesOnly: true, arrayFormat: 'indices' } | ||
), | ||
'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h' | ||
'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h', | ||
'encodeValuesOnly + indices' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] } | ||
{ a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, | ||
{ encodeValuesOnly: true, arrayFormat: 'brackets' } | ||
), | ||
'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h' | ||
'a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h', | ||
'encodeValuesOnly + brackets' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] }, | ||
{ encodeValuesOnly: true, arrayFormat: 'repeat' } | ||
), | ||
'a=b&c=d&c=e%3Df&f=g&f=h', | ||
'encodeValuesOnly + repeat' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, | ||
{ arrayFormat: 'indices' } | ||
), | ||
'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h', | ||
'no encodeValuesOnly + indices' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, | ||
{ arrayFormat: 'brackets' } | ||
), | ||
'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h', | ||
'no encodeValuesOnly + brackets' | ||
); | ||
st.equal( | ||
qs.stringify( | ||
{ a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }, | ||
{ arrayFormat: 'repeat' } | ||
), | ||
'a=b&c=d&c=e&f=g&f=h', | ||
'no encodeValuesOnly + repeat' | ||
); | ||
st.end(); | ||
@@ -889,9 +1137,15 @@ }); | ||
t.test('adds the right sentinel when instructed to and the charset is utf-8', function (st) { | ||
st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), 'utf8=%E2%9C%93&a=%C3%A6'); | ||
st.end(); | ||
}); | ||
t.test('`charsetSentinel` option', function (st) { | ||
st.equal( | ||
qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), | ||
'utf8=%E2%9C%93&a=%C3%A6', | ||
'adds the right sentinel when instructed to and the charset is utf-8' | ||
); | ||
t.test('adds the right sentinel when instructed to and the charset is iso-8859-1', function (st) { | ||
st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), 'utf8=%26%2310003%3B&a=%E6'); | ||
st.equal( | ||
qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), | ||
'utf8=%26%2310003%3B&a=%E6', | ||
'adds the right sentinel when instructed to and the charset is iso-8859-1' | ||
); | ||
st.end(); | ||
@@ -947,9 +1201,11 @@ }); | ||
st.equal(qs.stringify(obj, { encode: false }), 'a[b][c]=d&a[b][e]=f', 'no array, no arrayFormat'); | ||
st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'bracket' }), 'a[b][c]=d&a[b][e]=f', 'no array, bracket'); | ||
st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'brackets' }), 'a[b][c]=d&a[b][e]=f', 'no array, bracket'); | ||
st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'indices' }), 'a[b][c]=d&a[b][e]=f', 'no array, indices'); | ||
st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'no array, repeat'); | ||
st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), 'a[b][c]=d&a[b][e]=f', 'no array, comma'); | ||
st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat'); | ||
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'bracket' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, bracket'); | ||
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'brackets' }), 'a[b][][c]=d&a[b][][e]=f', 'array, bracket'); | ||
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices'); | ||
st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'array, repeat'); | ||
st.equal( | ||
@@ -967,7 +1223,18 @@ qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }), | ||
/* eslint no-sparse-arrays: 0 */ | ||
st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true }), 'a[1]=2&a[4]=1'); | ||
st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true }), 'a[1][b][2][c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c][1]=1'); | ||
st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1]=2&a[4]=1'); | ||
st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=2&a[]=1'); | ||
st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=2&a=1'); | ||
st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][b][2][c]=1'); | ||
st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][b][][c]=1'); | ||
st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[b][c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c][1]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c][]=1'); | ||
st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1'); | ||
st.end(); | ||
@@ -982,3 +1249,17 @@ }); | ||
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.withEmptyKeys, { encode: false, arrayFormat: 'indices' }), | ||
testCase.stringifyOutput.indices, | ||
'test case: ' + testCase.input + ', indices' | ||
); | ||
st.deepEqual( | ||
qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'brackets' }), | ||
testCase.stringifyOutput.brackets, | ||
'test case: ' + testCase.input + ', brackets' | ||
); | ||
st.deepEqual( | ||
qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'repeat' }), | ||
testCase.stringifyOutput.repeat, | ||
'test case: ' + testCase.input + ', repeat' | ||
); | ||
@@ -992,2 +1273,4 @@ st.end(); | ||
st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), '[][0]=2&[][1]=3&[a]=2'); | ||
st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3'); | ||
st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3&[a]=2'); | ||
@@ -994,0 +1277,0 @@ st.end(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
245120
1.65%698
5.12%30
42.86%3324
-22.43%1
Infinity%Updated