Skip to content

Commit

Permalink
feat: parseNumbers support includes & excludes options
Browse files Browse the repository at this point in the history
  • Loading branch information
daiwa233 committed Aug 5, 2023
1 parent c5c2efc commit 2aa0e6c
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 10 deletions.
9 changes: 8 additions & 1 deletion base.d.ts
Expand Up @@ -127,6 +127,7 @@ export type ParseOptions = {

/**
Parse the value as a number type instead of string type if it's a number.
Note: will always return a string if it exceeds the maximum safe integer in JavaScript
@default false
Expand All @@ -136,9 +137,15 @@ export type ParseOptions = {
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
queryString.parse('foo=1&bar=2', {parseNumbers: {includes: ['foo']}});
//=> {foo: 1, bar: '2'}
queryString.parse('foo=1&bar=2', {parseNumbers: {excludes: ['foo']}});
//=> {foo: '1', bar: 2}
```
*/
readonly parseNumbers?: boolean;
readonly parseNumbers?: boolean | { includes?: string[], excludes?: string[] };

/**
Parse the value as a boolean type instead of string type if it's a boolean.
Expand Down
35 changes: 28 additions & 7 deletions base.js
Expand Up @@ -300,11 +300,32 @@ function getHash(url) {
return hash;
}

function parseValue(value, options) {
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
value = Number(value);
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
value = value.toLowerCase() === 'true';
function getNumberValue (value) {
const numberValue = Number(value)

if (Number.isNaN(numberValue) || numberValue > Number.MAX_SAFE_INTEGER) {
return value
} else {
return numberValue
}
}

function parseValue(key, value, options, returnValue) {
if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
return value.toLowerCase() === 'true';
} else if (options.parseNumbers && (typeof value === 'string' && value.trim() !== '')) {
if (typeof options.parseNumbers === 'object') {
const { includes=Object.keys(returnValue), excludes=[] } = options.parseNumbers;

if (!includes.includes(key)) {
return value;
}
if (excludes.includes(key)) {
return value;
}
}

return getNumberValue(value)
}

return value;
Expand Down Expand Up @@ -370,10 +391,10 @@ export function parse(query, options) {
for (const [key, value] of Object.entries(returnValue)) {
if (typeof value === 'object' && value !== null) {
for (const [key2, value2] of Object.entries(value)) {
value[key2] = parseValue(value2, options);
value[key2] = parseValue(key2, value2, options, returnValue);
}
} else {
returnValue[key] = parseValue(value, options);
returnValue[key] = parseValue(key, value, options, returnValue);
}
}

Expand Down
11 changes: 9 additions & 2 deletions readme.md
Expand Up @@ -197,18 +197,25 @@ Supports both `Function` as a custom sorting function or `false` to disable sort

##### parseNumbers

Type: `boolean`\
Type: `boolean | { includes?: string[], excludes?: string[] }`\
Default: `false`

```js
import queryString from 'query-string';

queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
```

queryString.parse('foo=1&bar=2', {parseNumbers: {includes: ['foo']}});
//=> {foo: 1, bar: '2'}

queryString.parse('foo=1&bar=2', {parseNumbers: {excludes: ['foo']}});
//=> {foo: '1', bar: 2}
```
Parse the value as a number type instead of string type if it's a number.

> Note: will always return a string if it exceeds the maximum safe integer in JavaScript
##### parseBooleans

Type: `boolean`\
Expand Down
25 changes: 25 additions & 0 deletions test/parse.js
Expand Up @@ -324,6 +324,31 @@ test('NaN value returns as string if option is set', t => {
t.deepEqual(queryString.parse('foo= &bar=', {parseNumbers: true}), {foo: ' ', bar: ''});
});

test('exceed JavaScript\'s maximum safe integer value returns as string if option is set', t => {
t.deepEqual(queryString.parse('foo=9007199254740991', {parseNumbers: true}), {foo: 9007199254740991});
t.deepEqual(queryString.parse('foo=9007199254740992', {parseNumbers: true}), {foo: '9007199254740992'});
t.deepEqual(queryString.parse('foo=9007199254740992', {parseNumbers: {includes: ['foo']}}), {foo: '9007199254740992'});
t.deepEqual(queryString.parse('foo=9007199254740992', {parseNumbers: {excludes: ['foo']}}), {foo: '9007199254740992'});
});

test('only keys that match the includes configuration in parseNumbers will return number if parseNumbers.includes is set', t => {
t.deepEqual(queryString.parse('foo=1&bar=2', {parseNumbers: {includes: ['foo', 'bar']}}), {foo: 1, bar: 2});
t.deepEqual(queryString.parse('foo=1&bar=2', {parseNumbers: {includes: ['foo']}}), {foo: 1, bar: '2'});
t.deepEqual(queryString.parse('foo=null&bar=2', {parseNumbers: {includes: ['foo']}}), {foo: 'null', bar: '2'});
t.deepEqual(queryString.parse('foo=1&bar=2&baz=3', {parseNumbers: {includes: []}}), {foo: '1', bar: '2', 'baz': '3'});
t.deepEqual(queryString.parse('foo=1&bar=2&baz=3', {parseNumbers: {includes: ['foo'], excludes: ['baz']}}), {foo: 1, bar: '2', 'baz': '3'});
t.deepEqual(queryString.parse('foo=1&bar=2&baz=3', {parseNumbers: {includes: []}}), {foo: '1', bar: '2', 'baz': '3'});

});

test('only keys that does not match the excludes configuration in parseNumbers will return number if parseNumbers.excludes is set', t => {
t.deepEqual(queryString.parse('foo=1&bar=2', {parseNumbers: {excludes: ['foo', 'bar']}}), {foo: '1', bar: '2'});
t.deepEqual(queryString.parse('foo=1&bar=2', {parseNumbers: {excludes: ['foo']}}), {foo: '1', bar: 2});
t.deepEqual(queryString.parse('foo=1&bar=2', {parseNumbers: {excludes: []}}), {foo: 1, bar: 2});
t.deepEqual(queryString.parse('foo=1&bar=null', {parseNumbers: {excludes: ['foo']}}), {foo: '1', bar: 'null'});
t.deepEqual(queryString.parse('foo=1&bar=2&baz=3', {parseNumbers: {excludes: ['baz']}}), {foo: 1, bar: 2, 'baz': '3'});
});

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']});
Expand Down

0 comments on commit 2aa0e6c

Please sign in to comment.