Repository: sindresorhus/query-string (js)

Issue:
parseNumbers ignored with arrayFormat
The `parseNumbers` option does not get applied to values in an array.  e.g.:

```
const query = queryString.parse('?arrayOfNumbers=15,16,17&singleNumber=18', {
  parseNumbers: true,
  arrayFormat: 'comma',
});
```

Produces:

```
{
  arrayOfNumbers: ["15", "16", "17"],
  singleNumber: 18,
}
```

I was expecting it to produce:

```
{
  arrayOfNumbers: [15, 16, 17],
  singleNumber: 18,
}
```

Pull request description:
Make `parseNumbers` and `parseBooleans` options work with `arrayFormat`
fix #199 

Patch:
diff --git a/index.js b/index.js
index 35c13e1..b122f27 100644
--- a/index.js
+++ b/index.js
@@ -171,6 +171,16 @@ function extract(input) {
 	return input.slice(queryStart + 1);
 }
 
+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';
+	}
+
+	return value;
+}
+
 function parse(input, options) {
 	options = Object.assign({
 		decode: true,
@@ -201,14 +211,18 @@ function parse(input, options) {
 		// Missing `=` should be `null`:
 		// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
 		value = value === undefined ? null : decode(value, options);
+		formatter(decode(key, options), value, ret);
+	}
 
-		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';
+	for (const key of Object.keys(ret)) {
+		const value = ret[key];
+		if (typeof value === 'object' && value !== null) {
+			for (const k of Object.keys(value)) {
+				value[k] = parseValue(value[k], options);
+			}
+		} else {
+			ret[key] = parseValue(value, options);
 		}
-
-		formatter(decode(key, options), value, ret);
 	}
 
 	if (options.sort === false) {


Test patch:
diff --git a/test/parse.js b/test/parse.js
index 8273b71..5f714e3 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -246,6 +246,13 @@ test('NaN value returns as string if option is set', t => {
 	t.deepEqual(queryString.parse('foo=   &bar=', {parseNumbers: true}), {foo: '   ', bar: ''});
 });
 
+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[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]});
+});
+
 test('boolean value returns as string by default', t => {
 	t.deepEqual(queryString.parse('foo=true'), {foo: 'true'});
 });
@@ -255,7 +262,21 @@ test('boolean value returns as boolean if option is set', t => {
 	t.deepEqual(queryString.parse('foo=false&bar=true', {parseBooleans: true}), {foo: false, bar: true});
 });
 
+test('parseBooleans works with arrayFormat', t => {
+	t.deepEqual(queryString.parse('foo[]=true&foo[]=false&foo[]=true&bar=1', {parseBooleans: true, arrayFormat: 'bracket'}), {foo: [true, false, true], bar: '1'});
+	t.deepEqual(queryString.parse('foo=true,false,a', {parseBooleans: true, arrayFormat: 'comma'}), {foo: [true, false, 'a']});
+	t.deepEqual(queryString.parse('foo[0]=true&foo[1]=false&foo[2]', {parseBooleans: true, arrayFormat: 'index'}), {foo: [true, false, null]});
+	t.deepEqual(queryString.parse('foo=true&foo=false&foo=3', {parseBooleans: true}), {foo: [true, false, '3']});
+});
+
 test('boolean value returns as boolean and number value as number if both options are set', t => {
 	t.deepEqual(queryString.parse('foo=true&bar=1.12', {parseNumbers: true, parseBooleans: true}), {foo: true, bar: 1.12});
 	t.deepEqual(queryString.parse('foo=16.32&bar=false', {parseNumbers: true, parseBooleans: true}), {foo: 16.32, bar: false});
 });
+
+test('parseNumbers and parseBooleans can work with arrayFormat at the same time', t => {
+	t.deepEqual(queryString.parse('foo=true&foo=false&bar=1.12&bar=2', {parseNumbers: true, parseBooleans: true}), {foo: [true, false], bar: [1.12, 2]});
+	t.deepEqual(queryString.parse('foo[]=true&foo[]=false&foo[]=true&bar[]=1&bar[]=2', {parseNumbers: true, parseBooleans: true, arrayFormat: 'bracket'}), {foo: [true, false, true], bar: [1, 2]});
+	t.deepEqual(queryString.parse('foo=true,false&bar=1,2', {parseNumbers: true, parseBooleans: true, arrayFormat: 'comma'}), {foo: [true, false], bar: [1, 2]});
+	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]});
+});