Skip to content

Commit

Permalink
Add option to skip keys with the value of null in .stringify() (#215
Browse files Browse the repository at this point in the history
)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
yaodingyd and sindresorhus committed Nov 13, 2019
1 parent 35f4820 commit 20d8069
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 7 deletions.
24 changes: 24 additions & 0 deletions index.d.ts
Expand Up @@ -192,6 +192,28 @@ export interface StringifyOptions {
```
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;

/**
Skip keys with `null` as the value.
Note that keys with `undefined` as the value are always skipped.
@default false
@example
```
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```
*/
readonly skipNull?: boolean;
}

/**
Expand All @@ -204,5 +226,7 @@ export function stringify(

/**
Extract a query string from a URL that can be passed into `.parse()`.
Note: This behaviour can be changed with the `skipNull` option.
*/
export function extract(url: string): string;
22 changes: 16 additions & 6 deletions index.js
Expand Up @@ -8,7 +8,7 @@ function encoderForArrayFormat(options) {
case 'index':
return key => (result, value) => {
const index = result.length;
if (value === undefined) {
if (value === undefined || (options.skipNull && value === null)) {
return result;
}

Expand All @@ -24,7 +24,7 @@ function encoderForArrayFormat(options) {

case 'bracket':
return key => (result, value) => {
if (value === undefined) {
if (value === undefined || (options.skipNull && value === null)) {
return result;
}

Expand All @@ -36,12 +36,12 @@ function encoderForArrayFormat(options) {
};

case 'comma':
return key => (result, value, index) => {
return key => (result, value) => {
if (value === null || value === undefined || value.length === 0) {
return result;
}

if (index === 0) {
if (result.length === 0) {
return [[encode(key, options), '=', encode(value, options)].join('')];
}

Expand All @@ -50,7 +50,7 @@ function encoderForArrayFormat(options) {

default:
return key => (result, value) => {
if (value === undefined) {
if (value === undefined || (options.skipNull && value === null)) {
return result;
}

Expand Down Expand Up @@ -257,7 +257,17 @@ exports.stringify = (object, options) => {
}, options);

const formatter = encoderForArrayFormat(options);
const keys = Object.keys(object);

const objectCopy = Object.assign({}, object);
if (options.skipNull) {
for (const key of Object.keys(objectCopy)) {
if (objectCopy[key] === undefined || objectCopy[key] === null) {
delete objectCopy[key];
}
}
}

const keys = Object.keys(objectCopy);

if (options.sort !== false) {
keys.sort(options.sort);
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Expand Up @@ -22,6 +22,7 @@ expectType<string>(queryString.stringify({foo: 'bar'}, {arrayFormat: 'index'}));
expectType<string>(queryString.stringify({foo: 'bar'}, {arrayFormat: 'none'}));
expectType<string>(queryString.stringify({foo: 'bar'}, {arrayFormat: 'comma'}));
expectType<string>(queryString.stringify({foo: 'bar'}, {sort: false}));
expectType<string>(queryString.stringify({foo: 'bar'}, {skipNull: true}));
const order = ['c', 'a', 'b'];
expectType<string>(
queryString.stringify(
Expand Down
25 changes: 25 additions & 0 deletions readme.md
Expand Up @@ -204,10 +204,35 @@ queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});

If omitted, keys are sorted using `Array#sort()`, which means, converting them to strings and comparing strings in Unicode code point order.

##### skipNull

Skip keys with `null` as the value.

Note that keys with `undefined` as the value are always skipped.

Type: `boolean`<br>
Default: `false`

```js
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
```

```js
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```

### .extract(string)

Extract a query string from a URL that can be passed into `.parse()`.

Note: This behaviour can be changed with the `skipNull` option.

### .parseUrl(string, options?)

Extract the URL and the query string as an object.
Expand Down
68 changes: 67 additions & 1 deletion test/stringify.js
Expand Up @@ -128,7 +128,7 @@ test('array stringify representation with array commas', t => {

test('array stringify representation with array commas and null value', t => {
t.is(queryString.stringify({
foo: ['a', null, ''],
foo: [null, 'a', null, ''],
bar: [null]
}, {
arrayFormat: 'comma'
Expand Down Expand Up @@ -193,3 +193,69 @@ test('should disable sorting', t => {
sort: false
}), 'c=foo&b=bar&a=baz');
});

test('should ignore null when skipNull is set', t => {
t.is(queryString.stringify({
a: 1,
b: null,
c: 3
}, {
skipNull: true
}), 'a=1&c=3');
});

test('should ignore undefined when skipNull is set', t => {
t.is(queryString.stringify({
a: 1,
b: undefined,
c: 3
}, {
skipNull: true
}), 'a=1&c=3');
});

test('should ignore both null and undefined when skipNull is set', t => {
t.is(queryString.stringify({
a: undefined,
b: null
}, {
skipNull: true
}), '');
});

test('should ignore both null and undefined when skipNull is set for arrayFormat', t => {
t.is(queryString.stringify({
a: [undefined, null, 1, undefined, 2, null],
b: null,
c: 1
}, {
skipNull: true
}), 'a=1&a=2&c=1');

t.is(queryString.stringify({
a: [undefined, null, 1, undefined, 2, null],
b: null,
c: 1
}, {
skipNull: true,
arrayFormat: 'bracket'
}), 'a[]=1&a[]=2&c=1');

t.is(queryString.stringify({
a: [undefined, null, 1, undefined, 2, null],
b: null,
c: 1
}, {
skipNull: true,
arrayFormat: 'comma'
}), 'a=1,2&c=1');

t.is(queryString.stringify({
a: [undefined, null, 1, undefined, 2, null],
b: null,
c: 1
}, {
skipNull: true,
arrayFormat: 'index'
}), 'a[0]=1&a[1]=2&c=1');
});

0 comments on commit 20d8069

Please sign in to comment.