Skip to content

Commit

Permalink
Support custom array separator (#234)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
jpoehnelt and sindresorhus authored Feb 13, 2020
1 parent a1d108f commit 7712622
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 6 deletions.
36 changes: 34 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
```
Expand All @@ -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.
Expand Down Expand Up @@ -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:
```
Expand All @@ -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.
Expand Down
22 changes: 18 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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:
Expand Down Expand Up @@ -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;
};

Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`\
Expand Down Expand Up @@ -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`
Expand Down
17 changes: 17 additions & 0 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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]});
});
Expand Down Expand Up @@ -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: [
Expand Down
9 changes: 9 additions & 0 deletions test/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});

0 comments on commit 7712622

Please sign in to comment.