From 7cda9576307bde5e17cb1281156b03365b2423b4 Mon Sep 17 00:00:00 2001 From: Omar Mohamed <111138235+OmarMohamedoi@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:21:44 +0300 Subject: [PATCH 1/2] Add unit test for handling null and empty strings in arrayFormat: 'comma' --- test/stringify.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/stringify.js b/test/stringify.js index 5c3487b2..8a430242 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -126,6 +126,18 @@ test('array stringify representation with array commas', t => { }), 'bar=one,two&foo'); }); +test('array stringify representation with array commas and null/empty values', t => { + const result = queryString.stringify({ + list: ['item', null, 'last', ''] + }, { + arrayFormat: 'comma', + skipNull: false, + skipEmptyString: false + }); + + t.is(result, 'list=item,,last,'); +}); + test('array stringify representation with array commas and null value', t => { t.is(queryString.stringify({ foo: [null, 'a', null, ''], From a16dc73fe5e833b29c599d3e2ed0317bf5257da6 Mon Sep 17 00:00:00 2001 From: Omar Mohamed <111138235+OmarMohamedoi@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:45:35 +0300 Subject: [PATCH 2/2] Apply bug fix and resolve cherry-pick --- .js | 19 +++++++++++++++++++ index.d.ts | 5 +++++ index.js | 12 ++++++++++-- readme.md | 5 +++++ test/parse.js | 31 +++++++++++++++++++++++++++++-- test/stringify.js | 23 ++++++++++++++++++----- 6 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 .js diff --git a/.js b/.js new file mode 100644 index 00000000..fa7dcb0a --- /dev/null +++ b/.js @@ -0,0 +1,19 @@ +const queryString = require('./index'); + +// Example data that includes null and empty strings +const params = { + list: ['item', '', null, 'last'] +}; + +// Options to reproduce the bug +const options = { + arrayFormat: 'comma', + skipNull: false, + skipEmptyString: false +}; + +// Stringify the parameters with the options +const result = queryString.stringify(params, options); + +// Log the result to console +console.log(result); // Expected to incorrectly skip null and empty strings based on the bug diff --git a/index.d.ts b/index.d.ts index 026d287d..5db5cf2f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -229,6 +229,11 @@ export interface StringifyOptions { queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'}); //=> 'foo=1,2,3' + + queryString.stringify({foo: [1, null, '']}, {arrayFormat: 'comma'}); + //=> 'foo=1,,' + // Note that typing information for null values is lost + // and `.parse('foo=1,,')` would return `{foo: [1, '', '']}`. ``` - `separator`: Serialize arrays by separating elements with character: diff --git a/index.js b/index.js index 559ecd47..ed89dd1e 100644 --- a/index.js +++ b/index.js @@ -49,12 +49,20 @@ function encoderForArrayFormat(options) { case 'comma': case 'separator': return key => (result, value) => { - if (value === null || value === undefined || value.length === 0) { + if ( + value === undefined || + (options.skipNull && value === null) || + (options.skipEmptyString && value === '') + ) { return result; } if (result.length === 0) { - return [[encode(key, options), '=', encode(value, options)].join('')]; + return [[encode(key, options), '=', encode(value === null ? '' : value, options)].join('')]; + } + + if (value === null || value === '') { + return [[result, ''].join(options.arrayFormatSeparator)]; } return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; diff --git a/readme.md b/readme.md index 0914d746..7997b5dd 100644 --- a/readme.md +++ b/readme.md @@ -228,6 +228,11 @@ const queryString = require('query-string'); queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'}); //=> 'foo=1,2,3' + +queryString.stringify({foo: [1, null, '']}, {arrayFormat: 'comma'}); +//=> 'foo=1,,' +// Note that typing information for null values is lost +// and `.parse('foo=1,,')` would return `{foo: [1, '', '']}`. ``` - `'none'`: Serialize arrays by using duplicate keys: diff --git a/test/parse.js b/test/parse.js index 365eb2f7..3e8237e4 100644 --- a/test/parse.js +++ b/test/parse.js @@ -213,7 +213,7 @@ test('query strings having ordered index arrays and format option as `index`', t }), {bat: 'buz', foo: ['zero', 'two', 'one', 'three']}); }); -test('circuit parse -> stringify', t => { +test('circuit parse → stringify', t => { const original = 'foo[3]=foo&foo[2]&foo[1]=one&foo[0]=&bat=buz'; const sortedOriginal = 'bat=buz&foo[0]=&foo[1]=one&foo[2]&foo[3]=foo'; const expected = {bat: 'buz', foo: ['', 'one', null, 'foo']}; @@ -226,7 +226,7 @@ test('circuit parse -> stringify', t => { t.is(queryString.stringify(expected, options), sortedOriginal); }); -test('circuit original -> parse - > stringify -> sorted original', t => { +test('circuit original → parse → stringify → sorted original', t => { const original = 'foo[21474836471]=foo&foo[21474836470]&foo[1]=one&foo[0]=&bat=buz'; const sortedOriginal = 'bat=buz&foo[0]=&foo[1]=one&foo[2]&foo[3]=foo'; const options = { @@ -236,6 +236,33 @@ test('circuit original -> parse - > stringify -> sorted original', t => { t.deepEqual(queryString.stringify(queryString.parse(original, options), options), sortedOriginal); }); +test('circuit parse → stringify with array commas', t => { + const original = 'c=,a,,&b=&a='; + const sortedOriginal = 'a=&b=&c=,a,,'; + const expected = { + c: ['', 'a', '', ''], + b: '', + a: '' + }; + const options = { + arrayFormat: 'comma' + }; + + t.deepEqual(queryString.parse(original, options), expected); + + t.is(queryString.stringify(expected, options), sortedOriginal); +}); + +test('circuit original → parse → stringify with array commas → sorted original', t => { + const original = 'c=,a,,&b=&a='; + const sortedOriginal = 'a=&b=&c=,a,,'; + const options = { + arrayFormat: 'comma' + }; + + t.deepEqual(queryString.stringify(queryString.parse(original, options), options), sortedOriginal); +}); + test('decode keys and values', t => { t.deepEqual(queryString.parse('st%C3%A5le=foo'), {ståle: 'foo'}); t.deepEqual(queryString.parse('foo=%7B%ab%%7C%de%%7D+%%7Bst%C3%A5le%7D%'), {foo: '{%ab%|%de%} %{ståle}%'}); diff --git a/test/stringify.js b/test/stringify.js index 8a430242..4ab748e8 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -138,13 +138,26 @@ test('array stringify representation with array commas and null/empty values', t t.is(result, 'list=item,,last,'); }); -test('array stringify representation with array commas and null value', t => { +test('array stringify representation with array commas, null & empty string', t => { t.is(queryString.stringify({ - foo: [null, 'a', null, ''], - bar: [null] + c: [null, 'a', '', null], + b: [null], + a: [''] + }, { + arrayFormat: 'comma' + }), 'a=&b=&c=,a,,'); +}); + +test('array stringify representation with array commas, null & empty string (skip both)', t => { + t.is(queryString.stringify({ + c: [null, 'a', '', null], + b: [null], + a: [''] }, { + skipNull: true, + skipEmptyString: true, arrayFormat: 'comma' - }), 'foo=a'); + }), 'c=a'); }); test('array stringify representation with array commas and 0 value', t => { @@ -153,7 +166,7 @@ test('array stringify representation with array commas and 0 value', t => { bar: [null] }, { arrayFormat: 'comma' - }), 'foo=a,0'); + }), 'bar=&foo=a,,0'); }); test('array stringify representation with a bad array format', t => {