Repository: sindresorhus/query-string
Pull request #171 (2019-03-15 14:23:35)

Add `parseNumbers ` option to `.parse()`
Current `parse` would return all value as string but I would like an option to return number as number.

zepod: I may be worth it to update typescript types
sindresorhus: I don't think `number` is clear enough as an option name. It doesn't describe what it does. Need a more explicit name.
yaodingyd: How about `parseAsNumber`?
Andrew-Lahikainen: Would it make sense to make this option a little more generic so it can handle other primitives? Or do you think there is a possibility that someone might want number conversions and *not* boolean conversions for example.

If you want to have a generic name I think something like parseTypes or parsePrimitiveTypes.

If you want to handle each primitive separately, I think appending parse at the front might be acceptable?

E.g.:
* parseNumbers
* parseBooleans
* parseNulls
* parseUndefineds
* parseSymbols (this one probably doesn't make sense to implement)
sindresorhus: > Would it make sense to make this option a little more generic so it can handle other primitives?

I don't see us adding more than `number` and `boolean` parsing. It can quickly get ambiguous. I would have preferred to have them under one option, but I have feeling people have use-cases for only either.
sindresorhus: I prefer the `parseNumber` option name.
Andrew-Lahikainen: I prefer plural since this option parses all numbers, not just one. But that's being really nitpicky :p
sindresorhus: Alright, `parseNumbers`.
HHK1: Thanks for this option, it's awesome !
Are things missing before it can be merged ? Just to have an idea of when it will included in a release :)
sindresorhus: Pretty close to being done. The merge conflict needs to be resolved.

Patch:
diff --git a/index.d.ts b/index.d.ts
index e3558f2..dcf6c15 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -13,25 +13,25 @@ export interface ParseOptions {
 	 *
 	 *
 	 *    queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
-	 *    //=> foo: [1, 2, 3]
+	 *    //=> foo: ['1', '2', '3']
 	 *
 	 * - `index`: Parse arrays with index representation:
 	 *
 	 *
 	 *    queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
-	 *    //=> foo: [1, 2, 3]
+	 *    //=> foo: ['1', '2', '3']
 	 *
 	 * - `comma`: Parse arrays with elements separated by comma:
 	 *
 	 *
 	 *    queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
-	 *    //=> foo: [1, 2, 3]
+	 *    //=> foo: ['1', '2', '3']
 	 *
 	 * - `none`: Parse arrays with elements using duplicate keys:
 	 *
 	 *
 	 *    queryString.parse('foo=1&foo=2&foo=3');
-	 *    //=> foo: [1, 2, 3]
+	 *    //=> foo: ['1', '2', '3']
 	 */
 	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'none';
 
@@ -55,10 +55,21 @@ export interface ParseOptions {
 	 */
 	readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
 
+	/**
+	 * Parse the value as a number type instead of string type if it's a number.
+	 *
+	 * @default false
+	 *
+	 * @example
+	 *
+	 * queryString.parse('foo[]=1&foo[]=2&foo[]=3', {parseNumbers: true});
+	 * //=> foo: [1, 2, 3]
+	 */
+	readonly parseNumbers?: boolean;
 }
 
 export interface ParsedQuery {
-	readonly [key: string]: string | string[] | null | undefined;
+	readonly [key: string]: string | number | Array<string | number> | null | undefined;
 }
 
 /**
diff --git a/index.js b/index.js
index 451472b..677cc5b 100644
--- a/index.js
+++ b/index.js
@@ -175,7 +175,8 @@ function parse(input, options) {
 	options = Object.assign({
 		decode: true,
 		sort: true,
-		arrayFormat: 'none'
+		arrayFormat: 'none',
+		parseNumbers: false
 	}, options);
 
 	const formatter = parserForArrayFormat(options);
@@ -200,6 +201,10 @@ function parse(input, options) {
 		// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
 		value = value === undefined ? null : decode(value, options);
 
+		if (options.parseNumbers && !Number.isNaN(Number(value))) {
+			value = Number(value);
+		}
+
 		formatter(decode(key, options), value, ret);
 	}
 
diff --git a/readme.md b/readme.md
index 13c9694..c3a5fd7 100644
--- a/readme.md
+++ b/readme.md
@@ -72,28 +72,28 @@ Default: `'none'`
 
 ```js
 queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
-//=> foo: [1, 2, 3]
+//=> foo: ['1', '2', '3']
 ```
 
 - `'index'`: Parse arrays with index representation:
 
 ```js
 queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
-//=> foo: [1, 2, 3]
+//=> foo: ['1', '2', '3']
 ```
 
 - `'comma'`: Parse arrays with elements separated by comma:
 
 ```js
 queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
-//=> foo: [1, 2, 3]
+//=> foo: ['1', '2', '3']
 ```
 
 - `'none'`: Parse arrays with elements using duplicate keys:
 
 ```js
 queryString.parse('foo=1&foo=2&foo=3');
-//=> foo: [1, 2, 3]
+//=> foo: ['1', '2', '3']
 ```
 
 ##### sort
@@ -103,6 +103,18 @@ Default: `true`
 
 Supports both `Function` as a custom sorting function or `false` to disable sorting.
 
+##### parseNumbers
+
+Type: `boolean`<br>
+Default: `false`
+
+```js
+queryString.parse('foo[]=1&foo[]=2&foo[]=3', {parseNumbers: true});
+//=> foo: [1, 2, 3]
+```
+
+Parse the value as a number type instead of string type if it's a number.
+
 ### .stringify(object, [options])
 
 Stringify an object into a query string and sorting the keys.


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index 02b7f53..eddfc3c 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -51,6 +51,9 @@ expectType<queryString.ParsedQuery>(
 expectType<queryString.ParsedQuery>(
 	queryString.parse('?foo=bar', {arrayFormat: 'comma'})
 );
+expectType<queryString.ParsedQuery>(
+	queryString.parse('?foo=1', {parseNumbers: true})
+);
 
 // Parse URL
 expectType<queryString.ParsedUrl>(queryString.parseUrl('?foo=bar'));
@@ -70,6 +73,9 @@ expectType<queryString.ParsedUrl>(
 expectType<queryString.ParsedUrl>(
 	queryString.parseUrl('?foo=bar', {arrayFormat: 'comma'})
 );
+expectType<queryString.ParsedQuery>(
+	queryString.parse('?foo=1', {parseNumbers: true})
+);
 
 // Extract
 expectType<string>(queryString.extract('http://foo.bar/?abc=def&hij=klm'));
diff --git a/test/parse.js b/test/parse.js
index cea4621..96e8202 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -228,3 +228,19 @@ test('decode keys and values', t => {
 test('disable decoding of keys and values', t => {
 	t.deepEqual(queryString.parse('tags=postal%20office,burger%2C%20fries%20and%20coke', {decode: false}), {tags: 'postal%20office,burger%2C%20fries%20and%20coke'});
 });
+
+test('number value returns as string by default', t => {
+	t.deepEqual(queryString.parse('foo=1'), {foo: '1'});
+});
+
+test('number value returns as number if option is set', t => {
+	t.deepEqual(queryString.parse('foo=1', {parseNumbers: true}), {foo: 1});
+	t.deepEqual(queryString.parse('foo=12.3&bar=123e-1', {parseNumbers: true}), {foo: 12.3, bar: 12.3});
+	t.deepEqual(queryString.parse('foo=0x11&bar=12.00', {parseNumbers: true}), {foo: 17, bar: 12});
+});
+
+test('NaN value returns as string if option is set', t => {
+	t.deepEqual(queryString.parse('foo=null', {parseNumbers: true}), {foo: 'null'});
+	t.deepEqual(queryString.parse('foo=undefined', {parseNumbers: true}), {foo: 'undefined'});
+	t.deepEqual(queryString.parse('foo=100a&bar=100', {parseNumbers: true}), {foo: '100a', bar: 100});
+});