diff --git a/base.d.ts b/base.d.ts index 0803e2a..39267dd 100644 --- a/base.d.ts +++ b/base.d.ts @@ -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 @@ -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. diff --git a/base.js b/base.js index 0ca95bb..ee67891 100644 --- a/base.js +++ b/base.js @@ -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; @@ -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); } } diff --git a/readme.md b/readme.md index 85d9a71..7f2a753 100644 --- a/readme.md +++ b/readme.md @@ -197,7 +197,7 @@ 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 @@ -205,10 +205,17 @@ 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`\ diff --git a/test/parse.js b/test/parse.js index e4904e9..4227ce5 100644 --- a/test/parse.js +++ b/test/parse.js @@ -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']});