Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom array separator #234

Merged
merged 10 commits into from
Feb 13, 2020
Merged
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
});
});