Repository: sindresorhus/query-string (js)

Issue:
Query param ordering is changed on .parse
For example this:
```
?all=monkeys&-all=frank&make=canon&page=1&resultSize=20
```
becomes this:
```
{-all: "frank", all: "monkeys", make: "canon", page: "1", resultSize: "20"}
```

Expected behaviour is that it would produce this:
```
{all: "monkeys", -all: "frank", make: "canon", page: "1", resultSize: "20"}
```

It looks as though it's being sorted on purpose:
https://github.com/sindresorhus/query-string/blob/master/index.js#L205

I noticed that you have an option to disable sorting on `stringify`. Is it possible to have the same option on `parse`?

Pull request description:
Add `sort` option to `.parse()`
In this version we introduce a parse function option called `sort` that defaults to `true` (current default behaviour). When `sort` is `false`, query string keys are not sorted when parsed, and appear in the resulting object in the order they appear in the query string.

Fixes #176 
Closes #162

Patch:
diff --git a/.github/funding.yml b/.github/funding.yml
index 15edf6e..07c8db1 100644
--- a/.github/funding.yml
+++ b/.github/funding.yml
@@ -1,4 +1,4 @@
 github: sindresorhus
 open_collective: sindresorhus
-patreon: sindresorhus
+tidelift: npm/query-string
 custom: https://sindresorhus.com/donate
diff --git a/.github/security.md b/.github/security.md
new file mode 100644
index 0000000..5358dc5
--- /dev/null
+++ b/.github/security.md
@@ -0,0 +1,3 @@
+# Security Policy
+
+To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
diff --git a/index.d.ts b/index.d.ts
index 90b3658..5636e9d 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -34,6 +34,27 @@ export interface ParseOptions {
 	 *    //=> foo: [1, 2, 3]
 	 */
 	readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'none';
+
+	/**
+	 * Supports both `Function` as a custom sorting function or `false` to disable sorting.
+	 *
+	 * If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order.
+	 *
+	 * @default true
+	 *
+	 * @example
+	 *
+	 * const order = ['c', 'a', 'b'];
+	 * queryString.parse('?a=one&b=two&c=three', {
+	 * 	sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
+	 * });
+	 * // => {c: 'three', a: 'one', b: 'two'}
+	 *
+	 * queryString.parse('?a=one&c=three&b=two', {sort: false});
+	 * // => {a: 'one', c: 'three', b: 'two'}
+	 */
+	readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
+
 }
 
 export interface ParsedQuery {
@@ -133,7 +154,7 @@ export interface StringifyOptions {
  * Stringify an object into a query string and sorting the keys.
  */
 export function stringify(
-	object: {[key: string]: unknown},
+	object: {[key: string]: any},
 	options?: StringifyOptions
 ): string;
 
diff --git a/index.js b/index.js
index ba23c36..451472b 100644
--- a/index.js
+++ b/index.js
@@ -174,6 +174,7 @@ function extract(input) {
 function parse(input, options) {
 	options = Object.assign({
 		decode: true,
+		sort: true,
 		arrayFormat: 'none'
 	}, options);
 
@@ -202,7 +203,11 @@ function parse(input, options) {
 		formatter(decode(key, options), value, ret);
 	}
 
-	return Object.keys(ret).sort().reduce((result, key) => {
+	if (options.sort === false) {
+		return ret;
+	}
+
+	return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => {
 		const value = ret[key];
 		if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
 			// Sort object keys, not values
diff --git a/readme.md b/readme.md
index dcc0c88..0139c29 100644
--- a/readme.md
+++ b/readme.md
@@ -96,6 +96,13 @@ queryString.parse('foo=1&foo=2&foo=3');
 //=> foo: [1, 2, 3]
 ```
 
+##### sort
+
+Type: `Function | boolean`<br>
+Default: `true`
+
+Supports both `Function` as a custom sorting function or `false` to disable sorting.
+
 ### .stringify(object, [options])
 
 Stringify an object into a query string and sorting the keys.
@@ -232,3 +239,16 @@ queryString.stringify({foo: null});
 queryString.stringify({foo: undefined});
 //=> ''
 ```
+
+
+---
+
+<div align="center">
+	<b>
+		<a href="https://tidelift.com/subscription/pkg/npm-query-string?utm_source=npm-query-string&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
+	</b>
+	<br>
+	<sub>
+		Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
+	</sub>
+</div>


Test patch:
diff --git a/test/parse.js b/test/parse.js
index 415afdc..cea4621 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -24,6 +24,32 @@ test('parse multiple query string', t => {
 	});
 });
 
+test('parse multiple query string retain order when not sorted', t => {
+	const expectedKeys = ['b', 'a', 'c'];
+	const parsed = queryString.parse('b=foo&a=bar&c=yay', {sort: false});
+	Object.keys(parsed).forEach((key, index) => {
+		t.is(key, expectedKeys[index]);
+	});
+});
+
+test('parse multiple query string sorted keys', t => {
+	const fixture = ['a', 'b', 'c'];
+	const parsed = queryString.parse('a=foo&c=bar&b=yay');
+	Object.keys(parsed).forEach((key, index) => {
+		t.is(key, fixture[index]);
+	});
+});
+
+test('should sort parsed keys in given order', t => {
+	const fixture = ['c', 'a', 'b'];
+	const sort = (key1, key2) => fixture.indexOf(key1) - fixture.indexOf(key2);
+
+	const parsed = queryString.parse('a=foo&b=bar&c=yay', {sort});
+	Object.keys(parsed).forEach((key, index) => {
+		t.is(key, fixture[index]);
+	});
+});
+
 test('parse query string without a value', t => {
 	t.deepEqual(queryString.parse('foo'), {foo: null});
 	t.deepEqual(queryString.parse('foo&key'), {