diff --git a/index.d.ts b/index.d.ts index 3fb11f09..864cea78 100644 --- a/index.d.ts +++ b/index.d.ts @@ -36,6 +36,15 @@ export interface ParseOptions { //=> {foo: ['1', '2', '3']} ``` + - `separator`: Parse arrays with elements separated by a custom character: + + ``` + import queryString = require('query-string'); + + queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); + //=> {foo: ['1', '2', '3']} + ``` + - `none`: Parse arrays with elements using duplicate keys: ``` @@ -45,7 +54,14 @@ export interface ParseOptions { //=> {foo: ['1', '2', '3']} ``` */ - readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'none'; + readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none'; + + /** + The character used to separate array elements when using `{arrayFormat: 'separator'}`. + + @default , + */ + readonly arrayFormatSeparator?: 'string'; /** Supports both `Function` as a custom sorting function or `false` to disable sorting. @@ -188,6 +204,15 @@ export interface StringifyOptions { //=> 'foo=1,2,3' ``` + - `separator`: Serialize arrays by separating elements with character: + + ``` + import queryString = require('query-string'); + + queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSeparator: '|'}); + //=> 'foo=1|2|3' + ``` + - `none`: Serialize arrays by using duplicate keys: ``` @@ -197,7 +222,14 @@ export interface StringifyOptions { //=> 'foo=1&foo=2&foo=3' ``` */ - readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'none'; + readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none'; + + /** + The character used to separate array elements when using `{arrayFormat: 'separator'}`. + + @default , + */ + readonly arrayFormatSeparator?: 'string'; /** Supports both `Function` as a custom sorting function or `false` to disable sorting. diff --git a/index.js b/index.js index d19e1381..9c631628 100644 --- a/index.js +++ b/index.js @@ -36,6 +36,7 @@ function encoderForArrayFormat(options) { }; case 'comma': + case 'separator': return key => (result, value) => { if (value === null || value === undefined || value.length === 0) { return result; @@ -45,7 +46,7 @@ function encoderForArrayFormat(options) { return [[encode(key, options), '=', encode(value, options)].join('')]; } - return [[result, encode(value, options)].join(',')]; + return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; }; default: @@ -104,9 +105,10 @@ function parserForArrayFormat(options) { }; case 'comma': + case 'separator': return (key, value, accumulator) => { - const isArray = typeof value === 'string' && value.split('').indexOf(',') > -1; - const newValue = isArray ? value.split(',').map(item => decode(item, options)) : value === null ? value : decode(value, options); + const isArray = typeof value === 'string' && value.split('').indexOf(options.arrayFormatSeparator) > -1; + const newValue = isArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : value === null ? value : decode(value, options); accumulator[key] = newValue; }; @@ -122,6 +124,12 @@ function parserForArrayFormat(options) { } } +function validateArrayFormatSeparator(value) { + if (typeof value !== 'string' || value.length !== 1) { + throw new TypeError('arrayFormatSeparator must be single character string'); + } +} + function encode(value, options) { if (options.encode) { return options.strict ? strictUriEncode(value) : encodeURIComponent(value); @@ -196,10 +204,13 @@ function parse(input, options) { decode: true, sort: true, arrayFormat: 'none', + arrayFormatSeparator: ',', parseNumbers: false, parseBooleans: false }, options); + validateArrayFormatSeparator(options.arrayFormatSeparator); + const formatter = parserForArrayFormat(options); // Create an object with no prototype @@ -263,9 +274,12 @@ exports.stringify = (object, options) => { options = Object.assign({ encode: true, strict: true, - arrayFormat: 'none' + arrayFormat: 'none', + arrayFormatSeparator: ',' }, options); + validateArrayFormatSeparator(options.arrayFormatSeparator); + const formatter = encoderForArrayFormat(options); const objectCopy = Object.assign({}, object); diff --git a/readme.md b/readme.md index 1dfacf9e..3dd2ea0b 100644 --- a/readme.md +++ b/readme.md @@ -121,6 +121,15 @@ queryString.parse('foo=1,2,3', {arrayFormat: 'comma'}); //=> {foo: ['1', '2', '3']} ``` +- `'separator'`: Parse arrays with elements separated by a custom character: + +```js +const queryString = require('query-string'); + +queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'}); +//=> {foo: ['1', '2', '3']} +``` + - `'none'`: Parse arrays with elements using duplicate keys: ```js @@ -130,6 +139,13 @@ queryString.parse('foo=1&foo=2&foo=3'); //=> {foo: ['1', '2', '3']} ``` +##### arrayFormatSeparator + +Type: `string`\ +Default: `','` + +The character used to separate array elements when using `{arrayFormat: 'separator'}`. + ##### sort Type: `Function | boolean`\ @@ -228,6 +244,13 @@ queryString.stringify({foo: [1, 2, 3]}); //=> 'foo=1&foo=2&foo=3' ``` +##### arrayFormatSeparator + +Type: `string`\ +Default: `','` + +The character used to separate array elements when using `{arrayFormat: 'separator'}`. + ##### sort Type: `Function | boolean` diff --git a/test/parse.js b/test/parse.js index b2f4da02..3c442e76 100644 --- a/test/parse.js +++ b/test/parse.js @@ -143,6 +143,13 @@ test('query strings having comma separated arrays and format option as `comma`', }), {foo: ['bar', 'baz']}); }); +test('query strings having pipe separated arrays and format option as `separator`', t => { + t.deepEqual(queryString.parse('foo=bar|baz', { + arrayFormat: 'separator', + arrayFormatSeparator: '|' + }), {foo: ['bar', 'baz']}); +}); + test('query strings having brackets arrays with null and format option as `bracket`', t => { t.deepEqual(queryString.parse('bar[]&foo[]=a&foo[]&foo[]=', { arrayFormat: 'bracket' @@ -253,6 +260,7 @@ test('NaN value returns as string if option is set', t => { test('parseNumbers works with arrayFormat', t => { t.deepEqual(queryString.parse('foo[]=1&foo[]=2&foo[]=3&bar=1', {parseNumbers: true, arrayFormat: 'bracket'}), {foo: [1, 2, 3], bar: 1}); t.deepEqual(queryString.parse('foo=1,2,a', {parseNumbers: true, arrayFormat: 'comma'}), {foo: [1, 2, 'a']}); + t.deepEqual(queryString.parse('foo=1|2|a', {parseNumbers: true, arrayFormat: 'separator', arrayFormatSeparator: '|'}), {foo: [1, 2, 'a']}); t.deepEqual(queryString.parse('foo[0]=1&foo[1]=2&foo[2]', {parseNumbers: true, arrayFormat: 'index'}), {foo: [1, 2, null]}); t.deepEqual(queryString.parse('foo=1&foo=2&foo=3', {parseNumbers: true}), {foo: [1, 2, 3]}); }); @@ -285,6 +293,15 @@ test('parseNumbers and parseBooleans can work with arrayFormat at the same time' t.deepEqual(queryString.parse('foo[0]=true&foo[1]=false&bar[0]=1&bar[1]=2', {parseNumbers: true, parseBooleans: true, arrayFormat: 'index'}), {foo: [true, false], bar: [1, 2]}); }); +test('parse throws TypeError for invalid arrayFormatSeparator', t => { + t.throws(_ => queryString.parse('', {arrayFormatSeparator: ',,'}), { + instanceOf: TypeError + }); + t.throws(_ => queryString.parse('', {arrayFormatSeparator: []}), { + instanceOf: TypeError + }); +}); + test('query strings having comma encoded and format option as `comma`', t => { t.deepEqual(queryString.parse('foo=zero%2Cone,two%2Cthree', {arrayFormat: 'comma'}), { foo: [ diff --git a/test/stringify.js b/test/stringify.js index 09b6371b..147d2e25 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -259,3 +259,12 @@ test('should ignore both null and undefined when skipNull is set for arrayFormat arrayFormat: 'index' }), 'a[0]=1&a[1]=2&c=1'); }); + +test('stringify throws TypeError for invalid arrayFormatSeparator', t => { + t.throws(_ => queryString.stringify({}, {arrayFormatSeparator: ',,'}), { + instanceOf: TypeError + }); + t.throws(_ => queryString.stringify({}, {arrayFormatSeparator: []}), { + instanceOf: TypeError + }); +});