From 5a9ccf4eac5613a2893b82eb621721b3374e8f5c Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Fri, 25 Sep 2020 11:49:03 -0400 Subject: [PATCH 01/15] Added support for arrayFormat 'bracket-separator' --- .gitignore | 1 + benchmark.js | 5 ++++- index.d.ts | 30 +++++++++++++++++++++++++++--- index.js | 29 ++++++++++++++++++++++++++++- readme.md | 31 +++++++++++++++++++++++++++++++ test/parse.js | 30 ++++++++++++++++++++++++++++++ test/stringify.js | 18 ++++++++++++++++++ 7 files changed, 139 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 239ecff1..fb456779 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules yarn.lock +.idea/ diff --git a/benchmark.js b/benchmark.js index af120ea7..b1524608 100644 --- a/benchmark.js +++ b/benchmark.js @@ -20,6 +20,7 @@ const TEST_STRING = stringify(TEST_OBJECT); const TEST_BRACKETS_STRING = stringify(TEST_OBJECT, {arrayFormat: 'bracket'}); const TEST_INDEX_STRING = stringify(TEST_OBJECT, {arrayFormat: 'index'}); const TEST_COMMA_STRING = stringify(TEST_OBJECT, {arrayFormat: 'comma'}); +const TEST_BRACKET_SEPARATOR_STRING = stringify(TEST_OBJECT, {arrayFormat: 'bracket-separator'}); const TEST_URL = stringifyUrl({url: TEST_HOST, query: TEST_OBJECT}); // Creates a test case and adds it to the suite @@ -41,6 +42,7 @@ defineTestCase('parse', TEST_STRING, {decode: false}); defineTestCase('parse', TEST_BRACKETS_STRING, {arrayFormat: 'bracket'}); defineTestCase('parse', TEST_INDEX_STRING, {arrayFormat: 'index'}); defineTestCase('parse', TEST_COMMA_STRING, {arrayFormat: 'comma'}); +defineTestCase('parse', TEST_BRACKET_SEPARATOR_STRING, {arrayFormat: 'bracket-separator'}); // Stringify defineTestCase('stringify', TEST_OBJECT); @@ -51,6 +53,7 @@ defineTestCase('stringify', TEST_OBJECT, {skipEmptyString: true}); defineTestCase('stringify', TEST_OBJECT, {arrayFormat: 'bracket'}); defineTestCase('stringify', TEST_OBJECT, {arrayFormat: 'index'}); defineTestCase('stringify', TEST_OBJECT, {arrayFormat: 'comma'}); +defineTestCase('stringify', TEST_OBJECT, {arrayFormat: 'bracket-separator'}); // Extract defineTestCase('extract', TEST_URL); @@ -66,7 +69,7 @@ suite.on('cycle', event => { const {name, hz} = event.target; const opsPerSec = Math.round(hz).toLocaleString(); - console.log(name.padEnd(36, '_') + opsPerSec.padStart(12, '_') + ' ops/s'); + console.log(name.padEnd(46, '_') + opsPerSec.padStart(3, '_') + ' ops/s'); }); suite.run(); diff --git a/index.d.ts b/index.d.ts index 343a68ec..ba5cde0f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -45,6 +45,18 @@ export interface ParseOptions { //=> {foo: ['1', '2', '3']} ``` + - `bracket-separator`: Parse arrays (that are explicitly marked with brackets) with elements separated by a custom character: + + ``` + import queryString = require('query-string'); + + queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1']} + + queryString.parse('foo[]=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1', '2', '3']} + ``` + - `none`: Parse arrays with elements using duplicate keys: ``` @@ -54,7 +66,7 @@ export interface ParseOptions { //=> {foo: ['1', '2', '3']} ``` */ - readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none'; + readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'none'; /** The character used to separate array elements when using `{arrayFormat: 'separator'}`. @@ -231,7 +243,7 @@ export interface StringifyOptions { //=> 'foo=1,2,3' ``` - - `separator`: Serialize arrays by separating elements with character: + - `separator`: Serialize arrays by separating elements with character: ``` import queryString = require('query-string'); @@ -240,6 +252,18 @@ export interface StringifyOptions { //=> 'foo=1|2|3' ``` + - `separator`: Serialize arrays by putting an explicitly trailing bracket and separating elements with character: + + ``` + import queryString = require('query-string'); + + queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=1' + + queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=1|2|3' + ``` + - `none`: Serialize arrays by using duplicate keys: ``` @@ -249,7 +273,7 @@ export interface StringifyOptions { //=> 'foo=1&foo=2&foo=3' ``` */ - readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none'; + readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'none'; /** The character used to separate array elements when using `{arrayFormat: 'separator'}`. diff --git a/index.js b/index.js index de1d7b18..6c0af1f9 100644 --- a/index.js +++ b/index.js @@ -48,13 +48,18 @@ function encoderForArrayFormat(options) { case 'comma': case 'separator': + case 'bracket-separator': return key => (result, value) => { if (value === null || value === undefined || value.length === 0) { return result; } + const keyValueSep = options.arrayFormat === 'bracket-separator' ? + '[]=' : + '='; + if (result.length === 0) { - return [[encode(key, options), '=', encode(value, options)].join('')]; + return [[encode(key, options), keyValueSep, encode(value, options)].join('')]; } return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; @@ -127,6 +132,28 @@ function parserForArrayFormat(options) { accumulator[key] = newValue; }; + case 'bracket-separator': + return (key, value, accumulator) => { + const isArray = /(\[\])$/.exec(key); + key = key.replace(/\[\]$/, ''); + + if (!isArray) { + accumulator[key] = value ? decode(value, options) : value; + return; + } + + const arrValue = value === null ? + [] : + value.split(options.arrayFormatSeparator).map(item => decode(item, options)); + + if (accumulator[key] === undefined) { + accumulator[key] = arrValue; + return; + } + + accumulator[key] = [].concat(accumulator[key], arrValue); + }; + default: return (key, value, accumulator) => { if (accumulator[key] === undefined) { diff --git a/readme.md b/readme.md index 46e58b5b..e1cd3a3d 100644 --- a/readme.md +++ b/readme.md @@ -125,6 +125,19 @@ queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: //=> {foo: ['1', '2', '3']} ``` +- `'bracket-separator'`: Parse explicitly bracket-postfixed arrays with elements separated by a custom character: + +```js +const queryString = require('query-string'); + +//Can handle arrays on a single value to product explicit array +queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1']} + +queryString.parse('foo[]=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1', '2', '3']} +``` + - `'none'`: Parse arrays with elements using duplicate keys: ```js @@ -230,6 +243,24 @@ queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'}); //=> 'foo=1,2,3' ``` +- `'separator'`: Serialize arrays by separating elements with a custom character: + +```js +const queryString = require('query-string'); + +queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSeparator: '|'}); +//=> 'foo=1|2|3' +``` + +- `'bracket-separator'`: Serialize arrays by explicitly postfixing arrays with brackets and separating elements with a custom character: + +```js +const queryString = require('query-string'); + +queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=1|2|3' +``` + - `'none'`: Serialize arrays by using duplicate keys: ```js diff --git a/test/parse.js b/test/parse.js index 89641444..6938f25d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -174,6 +174,36 @@ test('query strings having indexed arrays and format option as `index`', t => { }), {foo: ['bar', 'baz']}); }); +test('query strings having brackets+separator arrays and format option as `bracket-separator` with 1 value', t => { + t.deepEqual(queryString.parse('foo[]=bar', { + arrayFormat: 'bracket-separator' + }), {foo: ['bar']}); +}); + +test('query strings having brackets+separator arrays and format option as `bracket-separator` with multiple values', t => { + t.deepEqual(queryString.parse('foo[]=bar,baz,,,biz', { + arrayFormat: 'bracket-separator' + }), {foo: ['bar', 'baz', '', '', 'biz']}); +}); + +test('query strings with multiple brackets+separator arrays and format option as `bracket-separator` using same key name', t => { + t.deepEqual(queryString.parse('foo[]=bar,baz&foo[]=biz,boz', { + arrayFormat: 'bracket-separator' + }), {foo: ['bar', 'baz', 'biz', 'boz']}); +}); + +test('query strings having an empty brackets+separator array and format option as `bracket-separator`', t => { + t.deepEqual(queryString.parse('foo[]', { + arrayFormat: 'bracket-separator' + }), {foo: []}); +}); + +test('query strings having a brackets+separator array and format option as `bracket-separator` with a single empty string', t => { + t.deepEqual(queryString.parse('foo[]=', { + arrayFormat: 'bracket-separator' + }), {foo: ['']}); +}); + test('query strings having = within parameters (i.e. GraphQL IDs)', t => { t.deepEqual(queryString.parse('foo=bar=&foo=ba=z='), {foo: ['bar=', 'ba=z=']}); }); diff --git a/test/stringify.js b/test/stringify.js index 5c3487b2..6b550e51 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -159,6 +159,24 @@ test('array stringify representation with array indexes and sparse array', t => t.is(queryString.stringify({bar: fixture}, {arrayFormat: 'index'}), 'bar[0]=one&bar[1]=two&bar[2]=three'); }); +test('array stringify representation with brackets and separators with single value', t => { + t.is(queryString.stringify({ + foo: null, + bar: ['one'] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]=one&foo'); +}); + +test('array stringify representation with brackets and separators with multiple values', t => { + t.is(queryString.stringify({ + foo: null, + bar: ['one', 'two', 'three'] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]=one,two,three&foo'); +}); + test('should sort keys in given order', t => { const fixture = ['c', 'a', 'b']; const sort = (key1, key2) => fixture.indexOf(key1) - fixture.indexOf(key2); From d67e84d287f28686359b931f55268941620683e5 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Fri, 25 Sep 2020 12:24:23 -0400 Subject: [PATCH 02/15] match the decoding pattern of comma/separator --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 6c0af1f9..c1fbfa98 100644 --- a/index.js +++ b/index.js @@ -273,7 +273,7 @@ function parse(input, options) { // Missing `=` should be `null`: // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters - value = value === undefined ? null : ['comma', 'separator'].includes(options.arrayFormat) ? value : decode(value, options); + value = value === undefined ? null : ['comma', 'separator', 'bracket-separator'].includes(options.arrayFormat) ? value : decode(value, options); formatter(decode(key, options), value, ret); } From 20b51fcdeeea2155e3094131b05229a7e652dbde Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Fri, 25 Sep 2020 12:34:02 -0400 Subject: [PATCH 03/15] Fix roundtrip parse/stringify for arrays with empty strings --- index.js | 18 +++++++++++++++++- test/stringify.js | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index c1fbfa98..ef91b332 100644 --- a/index.js +++ b/index.js @@ -48,7 +48,6 @@ function encoderForArrayFormat(options) { case 'comma': case 'separator': - case 'bracket-separator': return key => (result, value) => { if (value === null || value === undefined || value.length === 0) { return result; @@ -65,6 +64,23 @@ function encoderForArrayFormat(options) { return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; }; + case 'bracket-separator': + return key => (result, value) => { + if (value === null || value === undefined) { // Explicitly allow empty strings + return result; + } + + const keyValueSep = options.arrayFormat === 'bracket-separator' ? + '[]=' : + '='; + + if (result.length === 0) { + return [[encode(key, options), keyValueSep, encode(value, options)].join('')]; + } + + return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; + }; + default: return key => (result, value) => { if ( diff --git a/test/stringify.js b/test/stringify.js index 6b550e51..16ca2148 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -177,6 +177,24 @@ test('array stringify representation with brackets and separators with multiple }), 'bar[]=one,two,three&foo'); }); +test('array stringify representation with brackets and separators with a single empty string', t => { + t.is(queryString.stringify({ + foo: null, + bar: [''] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]=&foo'); +}); + +test('array stringify representation with brackets and separators with a multiple empty string', t => { + t.is(queryString.stringify({ + foo: null, + bar: ['', 'two', ''] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]=,two,&foo'); +}); + test('should sort keys in given order', t => { const fixture = ['c', 'a', 'b']; const sort = (key1, key2) => fixture.indexOf(key1) - fixture.indexOf(key2); From 9eafe5f95db15be5ea507dd925b82b0a7b308a67 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:11:43 -0500 Subject: [PATCH 04/15] Removed modification of gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index fb456779..239ecff1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules yarn.lock -.idea/ From c772fbd150eb9944565f5c929dfc1161f4ebff5f Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:15:55 -0500 Subject: [PATCH 05/15] Fixed incorrect references to 'separator' in documentation --- index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index ba5cde0f..105ee9b9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -53,7 +53,7 @@ export interface ParseOptions { queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} - queryString.parse('foo[]=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); + queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} ``` @@ -252,7 +252,7 @@ export interface StringifyOptions { //=> 'foo=1|2|3' ``` - - `separator`: Serialize arrays by putting an explicitly trailing bracket and separating elements with character: + - `bracket-separator`: Serialize arrays by putting an explicitly trailing bracket and separating elements with character: ``` import queryString = require('query-string'); From 2a317b86d630fed335ed334315fefe2c5d5fe199 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:20:20 -0500 Subject: [PATCH 06/15] Added parse and stringify examples with multiple values. --- index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.d.ts b/index.d.ts index 105ee9b9..ca35a2ca 100644 --- a/index.d.ts +++ b/index.d.ts @@ -55,6 +55,9 @@ export interface ParseOptions { queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} + + queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']} ``` - `none`: Parse arrays with elements using duplicate keys: @@ -262,6 +265,9 @@ export interface StringifyOptions { queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3' + + queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=1|2|3&bar=fluffy&baz[]=4' ``` - `none`: Serialize arrays by using duplicate keys: From 14d9d9ada1d79b95826fe0d9adf31d5529bbea7a Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:40:07 -0500 Subject: [PATCH 07/15] Added support for {foo: []} -> ?foo[] --- index.d.ts | 6 ++++++ index.js | 4 ++++ test/stringify.js | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/index.d.ts b/index.d.ts index ca35a2ca..bf78aee6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,6 +50,9 @@ export interface ParseOptions { ``` import queryString = require('query-string'); + queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: []} + queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} @@ -260,6 +263,9 @@ export interface StringifyOptions { ``` import queryString = require('query-string'); + queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]' + queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1' diff --git a/index.js b/index.js index ef91b332..1159cf08 100644 --- a/index.js +++ b/index.js @@ -371,6 +371,10 @@ exports.stringify = (object, options) => { } if (Array.isArray(value)) { + if (value.length === 0 && options.arrayFormat === 'bracket-separator') { + return encode(key, options) + '[]'; + } + return value .reduce(formatter(key), []) .join('&'); diff --git a/test/stringify.js b/test/stringify.js index 16ca2148..1c0e5676 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -159,6 +159,15 @@ test('array stringify representation with array indexes and sparse array', t => t.is(queryString.stringify({bar: fixture}, {arrayFormat: 'index'}), 'bar[0]=one&bar[1]=two&bar[2]=three'); }); +test('array stringify representation with brackets and separators with empty array', t => { + t.is(queryString.stringify({ + foo: null, + bar: [] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]&foo'); +}); + test('array stringify representation with brackets and separators with single value', t => { t.is(queryString.stringify({ foo: null, From ad468995cc74422fadb295e62ff67d28a257dfd1 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:44:59 -0500 Subject: [PATCH 08/15] Added documentation for null vs empty string behavior in arrays --- index.d.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index bf78aee6..20b24e88 100644 --- a/index.d.ts +++ b/index.d.ts @@ -59,7 +59,10 @@ export interface ParseOptions { queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} - queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + queryString.parse('foo[]=1||3||5', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1', '', 3, '', '5']} + + queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']} ``` @@ -272,7 +275,11 @@ export interface StringifyOptions { queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3' - queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + queryString.stringify({foo: [1, '', 3, null, null, 5]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=1||3|6' + // Note that the nulls were dropped but the empty string was not. + + queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3&bar=fluffy&baz[]=4' ``` From 73462294c4fd1e7c8729751dde4c303c4b9423f7 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Thu, 14 Jan 2021 00:49:26 -0500 Subject: [PATCH 09/15] Added test for dropping null array item dropping. --- test/stringify.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/stringify.js b/test/stringify.js index 1c0e5676..b14bb8aa 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -204,6 +204,15 @@ test('array stringify representation with brackets and separators with a multipl }), 'bar[]=,two,&foo'); }); +test('array stringify representation with brackets and separators with dropped null values', t => { + t.is(queryString.stringify({ + foo: null, + bar: ['one', null, 'three', null, '', 'six'] + }, { + arrayFormat: 'bracket-separator' + }), 'bar[]=one,three,,six&foo'); +}); + test('should sort keys in given order', t => { const fixture = ['c', 'a', 'b']; const sort = (key1, key2) => fixture.indexOf(key1) - fixture.indexOf(key2); From fe63b6dd1395768109debeffb5ac41df11c57e2d Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Mon, 15 Mar 2021 12:40:41 -0400 Subject: [PATCH 10/15] Apply suggestions from code review Co-authored-by: Sindre Sorhus --- index.d.ts | 6 +++--- readme.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 20b24e88..f9278a21 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,7 +50,7 @@ export interface ParseOptions { ``` import queryString = require('query-string'); - queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: []} queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); @@ -252,7 +252,7 @@ export interface StringifyOptions { //=> 'foo=1,2,3' ``` - - `separator`: Serialize arrays by separating elements with character: + - `separator`: Serialize arrays by separating elements with character: ``` import queryString = require('query-string'); @@ -266,7 +266,7 @@ export interface StringifyOptions { ``` import queryString = require('query-string'); - queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]' queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); diff --git a/readme.md b/readme.md index e1cd3a3d..afde90f7 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: ```js const queryString = require('query-string'); -//Can handle arrays on a single value to product explicit array +// Can handle arrays on a single value to product explicit array queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} From 4cd07c1e3728cfadc96a0e3a48b96a5a01f59dc2 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Mon, 15 Mar 2021 12:53:40 -0400 Subject: [PATCH 11/15] Merged comma/separator and brackset-separator encoding cases. --- index.js | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 1159cf08..5fb04dde 100644 --- a/index.js +++ b/index.js @@ -48,29 +48,19 @@ function encoderForArrayFormat(options) { case 'comma': case 'separator': - return key => (result, value) => { - if (value === null || value === undefined || value.length === 0) { - return result; - } - - const keyValueSep = options.arrayFormat === 'bracket-separator' ? - '[]=' : - '='; - - if (result.length === 0) { - return [[encode(key, options), keyValueSep, encode(value, options)].join('')]; - } + case 'bracket-separator': { + const isBracketSeparator = options.arrayFormat === 'bracket-separator'; - return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; - }; - - case 'bracket-separator': return key => (result, value) => { - if (value === null || value === undefined) { // Explicitly allow empty strings + if ( + value === null || + value === undefined || + (value.length === 0 && !isBracketSeparator) // Explicitly allow empty strings for bracket-separator only + ) { return result; } - const keyValueSep = options.arrayFormat === 'bracket-separator' ? + const keyValueSep = isBracketSeparator ? '[]=' : '='; @@ -80,6 +70,7 @@ function encoderForArrayFormat(options) { return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; }; + } default: return key => (result, value) => { From 9204a8fc78c232aeb5bf25dd2b5aa24fe815c0e1 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Mon, 15 Mar 2021 13:04:55 -0400 Subject: [PATCH 12/15] Synced README and index.d.ts documentation --- index.d.ts | 2 +- readme.md | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index f9278a21..383f2704 100644 --- a/index.d.ts +++ b/index.d.ts @@ -261,7 +261,7 @@ export interface StringifyOptions { //=> 'foo=1|2|3' ``` - - `bracket-separator`: Serialize arrays by putting an explicitly trailing bracket and separating elements with character: + - `bracket-separator`: Serialize arrays by explicitly post-fixing array names with brackets and separating elements with a custom character: ``` import queryString = require('query-string'); diff --git a/readme.md b/readme.md index afde90f7..5e81c003 100644 --- a/readme.md +++ b/readme.md @@ -125,17 +125,25 @@ queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: //=> {foo: ['1', '2', '3']} ``` -- `'bracket-separator'`: Parse explicitly bracket-postfixed arrays with elements separated by a custom character: +- `'bracket-separator'`: Parse arrays (that are explicitly marked with brackets) with elements separated by a custom character: ```js const queryString = require('query-string'); -// Can handle arrays on a single value to product explicit array +queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: []} + queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} -queryString.parse('foo[]=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); +queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} + +queryString.parse('foo[]=1||3||5', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1', '', 3, '', '5']} + +queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']} ``` - `'none'`: Parse arrays with elements using duplicate keys: @@ -252,13 +260,26 @@ queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSe //=> 'foo=1|2|3' ``` -- `'bracket-separator'`: Serialize arrays by explicitly postfixing arrays with brackets and separating elements with a custom character: +- `'bracket-separator'`: Serialize arrays by explicitly post-fixing array names with brackets and separating elements with a custom character: ```js const queryString = require('query-string'); +queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]' + +queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=1' + queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3' + +queryString.stringify({foo: [1, '', 3, null, null, 5]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=1||3|6' +// Note that the nulls were dropped but the empty string was not. + +queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=1|2|3&bar=fluffy&baz[]=4' ``` - `'none'`: Serialize arrays by using duplicate keys: From 79007d8787aa58718620ac6ba475b319bca4f299 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Wed, 17 Mar 2021 11:24:42 -0400 Subject: [PATCH 13/15] Updated PR to include suggested changes. --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 5fb04dde..7ad34f25 100644 --- a/index.js +++ b/index.js @@ -141,7 +141,7 @@ function parserForArrayFormat(options) { case 'bracket-separator': return (key, value, accumulator) => { - const isArray = /(\[\])$/.exec(key); + const isArray = /(\[\])$/.test(key); key = key.replace(/\[\]$/, ''); if (!isArray) { @@ -149,16 +149,16 @@ function parserForArrayFormat(options) { return; } - const arrValue = value === null ? + const arrayValue = value === null ? [] : value.split(options.arrayFormatSeparator).map(item => decode(item, options)); if (accumulator[key] === undefined) { - accumulator[key] = arrValue; + accumulator[key] = arrayValue; return; } - accumulator[key] = [].concat(accumulator[key], arrValue); + accumulator[key] = [].concat(accumulator[key], arrayValue); }; default: From 2078b243a3a0a893dc33359862651fff6d74ff85 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Wed, 17 Mar 2021 12:46:24 -0400 Subject: [PATCH 14/15] Fixed tests to respect new defaults for skipNull and skipEmptyString --- test/stringify.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/stringify.js b/test/stringify.js index 31b8bc05..c8751eb7 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -217,13 +217,24 @@ test('array stringify representation with brackets and separators with a multipl }), 'bar[]=,two,&foo'); }); +test('array stringify representation with brackets and separators with dropped empty strings', t => { + t.is(queryString.stringify({ + foo: null, + bar: ['', 'two', ''] + }, { + arrayFormat: 'bracket-separator', + skipEmptyString: true + }), 'bar[]=two&foo'); +}); + test('array stringify representation with brackets and separators with dropped null values', t => { t.is(queryString.stringify({ foo: null, bar: ['one', null, 'three', null, '', 'six'] }, { - arrayFormat: 'bracket-separator' - }), 'bar[]=one,three,,six&foo'); + arrayFormat: 'bracket-separator', + skipNull: true + }), 'bar[]=one,three,,six'); }); test('should sort keys in given order', t => { From f2cc3286e55f23e39c1916e73a3ada1a8bb063e8 Mon Sep 17 00:00:00 2001 From: Austin Keener Date: Wed, 17 Mar 2021 12:55:57 -0400 Subject: [PATCH 15/15] Updated documentation to match new default skipNull/skipEmptyString behavior --- index.d.ts | 16 ++++++++++++---- readme.md | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index d289713b..847336d4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -53,14 +53,17 @@ export interface ParseOptions { queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: []} + queryString.parse('foo[]=', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: ['']} + queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} - queryString.parse('foo[]=1||3||5', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); - //=> {foo: ['1', '', 3, '', '5']} + queryString.parse('foo[]=1||3|||6', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1', '', 3, '', '', '6']} queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']} @@ -274,15 +277,20 @@ export interface StringifyOptions { queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]' + queryString.stringify({foo: ['']}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=' + queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1' queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3' - queryString.stringify({foo: [1, '', 3, null, null, 5]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); + //=> 'foo[]=1||3|||6' + + queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|', skipNull: true}); //=> 'foo[]=1||3|6' - // Note that the nulls were dropped but the empty string was not. queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3&bar=fluffy&baz[]=4' diff --git a/readme.md b/readme.md index 90b67112..600a971a 100644 --- a/readme.md +++ b/readme.md @@ -146,14 +146,17 @@ const queryString = require('query-string'); queryString.parse('foo[]', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: []} +queryString.parse('foo[]=', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: ['']} + queryString.parse('foo[]=1', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1']} queryString.parse('foo[]=1|2|3', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3']} -queryString.parse('foo[]=1||3||5', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); -//=> {foo: ['1', '', 3, '', '5']} +queryString.parse('foo[]=1||3|||6', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1', '', 3, '', '', '6']} queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']} @@ -286,15 +289,20 @@ const queryString = require('query-string'); queryString.stringify({foo: []}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]' +queryString.stringify({foo: ['']}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=' + queryString.stringify({foo: [1]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1' queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3' -queryString.stringify({foo: [1, '', 3, null, null, 5]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); +//=> 'foo[]=1||3|||6' + +queryString.stringify({foo: [1, '', 3, null, null, 6]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|', skipNull: true}); //=> 'foo[]=1||3|6' -// Note that the nulls were dropped but the empty string was not. queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: 'bracket-separator', arrayFormatSeparator: '|'}); //=> 'foo[]=1|2|3&bar=fluffy&baz[]=4'