Repository: sindresorhus/query-string
Pull request #335 (2021-12-22 23:16:57)

Add support for parameters with an explicit `:list` marker
…plicit :list marker.

This explicit marker is used in the Python-based Zope Application Server (https://zope.org) and in Plone (https://plone.org).

Thanks!

sneridagh: @sindresorhus would you mind to take a look? Thanks!
sindresorhus: You also need to update index.d.ts
sneridagh: > You also need to update index.d.ts

Done!

Patch:
diff --git a/index.d.ts b/index.d.ts
index 847336d..5633c5e 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -69,6 +69,15 @@ export interface ParseOptions {
 		//=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']}
 		```
 
+	- `colon-list-separator`: Parse arrays with parameter names that are explicitly marked with `:list`:
+
+		```
+		import queryString = require('query-string');
+
+		queryString.parse('foo:list=one&foo:list=two', {arrayFormat: 'colon-list-separator'});
+		//=> {foo: ['one', 'two']}
+		```
+
 	- `none`: Parse arrays with elements using duplicate keys:
 
 		```
@@ -78,7 +87,7 @@ export interface ParseOptions {
 		//=> {foo: ['1', '2', '3']}
 		```
 	*/
-	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'none';
+	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'colon-list-separator' | 'none';
 
 	/**
 	The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@@ -296,6 +305,15 @@ export interface StringifyOptions {
 		//=> 'foo[]=1|2|3&bar=fluffy&baz[]=4'
 		```
 
+	- `colon-list-separator`: Serialize arrays with parameter names that are explicitly marked with `:list`:
+
+		```js
+		import queryString = require('query-string');
+
+		queryString.stringify({foo: ['one', 'two']}, {arrayFormat: 'colon-list-separator'});
+		//=> 'foo:list=one&foo:list=two'
+		```
+
 	- `none`: Serialize arrays by using duplicate keys:
 
 		```
@@ -305,7 +323,7 @@ export interface StringifyOptions {
 		//=> 'foo=1&foo=2&foo=3'
 		```
 	*/
-	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'none';
+	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'colon-list-separator' | 'none';
 
 	/**
 	The character used to separate array elements when using `{arrayFormat: 'separator'}`.
diff --git a/index.js b/index.js
index cc57637..d45e67d 100644
--- a/index.js
+++ b/index.js
@@ -49,6 +49,23 @@ function encoderForArrayFormat(options) {
 				return [...result, [encode(key, options), '[]=', encode(value, options)].join('')];
 			};
 
+		case 'colon-list-separator':
+			return key => (result, value) => {
+				if (
+					value === undefined ||
+					(options.skipNull && value === null) ||
+					(options.skipEmptyString && value === '')
+				) {
+					return result;
+				}
+
+				if (value === null) {
+					return [...result, [encode(key, options), ':list='].join('')];
+				}
+
+				return [...result, [encode(key, options), ':list=', encode(value, options)].join('')];
+			};
+
 		case 'comma':
 		case 'separator':
 		case 'bracket-separator': {
@@ -135,6 +152,24 @@ function parserForArrayFormat(options) {
 				accumulator[key] = [].concat(accumulator[key], value);
 			};
 
+		case 'colon-list-separator':
+			return (key, value, accumulator) => {
+				result = /(:list)$/.exec(key);
+				key = key.replace(/:list$/, '');
+
+				if (!result) {
+					accumulator[key] = value;
+					return;
+				}
+
+				if (accumulator[key] === undefined) {
+					accumulator[key] = [value];
+					return;
+				}
+
+				accumulator[key] = [].concat(accumulator[key], value);
+			};
+
 		case 'comma':
 		case 'separator':
 			return (key, value, accumulator) => {
diff --git a/readme.md b/readme.md
index 3af49af..f696aae 100644
--- a/readme.md
+++ b/readme.md
@@ -184,6 +184,15 @@ queryString.parse('foo[]=1|2|3&bar=fluffy&baz[]=4', {arrayFormat: 'bracket-separ
 //=> {foo: ['1', '2', '3'], bar: 'fluffy', baz:['4']}
 ```
 
+- `'colon-list-separator'`: Parse arrays with parameter names that are explicitly marked with `:list`:
+
+```js
+const queryString = require('query-string');
+
+queryString.parse('foo:list=one&foo:list=two', {arrayFormat: 'colon-list-separator'});
+//=> {foo: ['one', 'two']}
+```
+
 - `'none'`: Parse arrays with elements using duplicate keys:
 
 ```js
@@ -330,6 +339,15 @@ queryString.stringify({foo: [1, 2, 3], bar: 'fluffy', baz: [4]}, {arrayFormat: '
 //=> 'foo[]=1|2|3&bar=fluffy&baz[]=4'
 ```
 
+- `'colon-list-separator'`: Serialize arrays with parameter names that are explicitly marked with `:list`:
+
+```js
+const queryString = require('query-string');
+
+queryString.stringify({foo: ['one', 'two']}, {arrayFormat: 'colon-list-separator'});
+//=> 'foo:list=one&foo:list=two'
+```
+
 - `'none'`: Serialize arrays by using duplicate keys:
 
 ```js


Test patch:
diff --git a/test/parse.js b/test/parse.js
index 731a848..9986efd 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -390,3 +390,11 @@ test('value separated by encoded comma will not be parsed as array with `arrayFo
 		id: [1, 2, 3]
 	});
 });
+
+test('query strings having (:list) colon-list-separator arrays', t => {
+	t.deepEqual(queryString.parse('bar:list=one&bar:list=two', {arrayFormat: 'colon-list-separator'}), {bar: ['one', 'two']});
+});
+
+test('query strings having (:list) colon-list-separator arrays including null values', t => {
+	t.deepEqual(queryString.parse('bar:list=one&bar:list=two&foo', {arrayFormat: 'colon-list-separator'}), {bar: ['one', 'two'], foo: null});
+});
diff --git a/test/stringify.js b/test/stringify.js
index c8751eb..e3f2a61 100644
--- a/test/stringify.js
+++ b/test/stringify.js
@@ -400,3 +400,27 @@ test('stringify throws TypeError for invalid arrayFormatSeparator', t => {
 		instanceOf: TypeError
 	});
 });
+
+test('array stringify representation with (:list) colon-list-separator', t => {
+	t.is(queryString.stringify({
+		foo: null,
+		bar: ['one', 'two']
+	}, {
+		arrayFormat: 'colon-list-separator'
+	}), 'bar:list=one&bar:list=two&foo');
+});
+
+test('array stringify representation with (:list) colon-list-separator with null values', t => {
+	t.is(queryString.stringify({
+		foo: null,
+		bar: ['one', '']
+	}, {
+		arrayFormat: 'colon-list-separator'
+	}), 'bar:list=one&bar:list=&foo');
+	t.is(queryString.stringify({
+		foo: null,
+		bar: ['one', null]
+	}, {
+		arrayFormat: 'colon-list-separator'
+	}), 'bar:list=one&bar:list=&foo');
+});