Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
export interface ParseOptions {
/**
* Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component).
*
* @default true
*/
readonly decode?: boolean;

/**
* Supports both `index` for an indexed array representation or `bracket` for a *bracketed* array representation.
*
* @default 'none'
*
* - `bracket`: stands for parsing correctly arrays with bracket representation on the query string, such as:
*
*
* queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
* //=> foo: [1,2,3]
*
* - `index`: stands for parsing taking the index into account, such as:
*
*
* queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
* //=> foo: [1,2,3]
*
* - `none`: is the **default** option and removes any bracket representation, such as:
*
*
* queryString.parse('foo=1&foo=2&foo=3');
* //=> foo: [1,2,3]
*/
readonly arrayFormat?: 'bracket' | 'index' | 'none';
}

export interface ParsedQuery {
readonly [key: string]: string | string[] | undefined;
}

/**
* Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
*
* The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.
*
* @param query - The query string to parse.
*/
export function parse(query: string, options?: ParseOptions): ParsedQuery;

export interface ParsedUrl {
readonly url: string;
readonly query: ParsedQuery;
}

/**
* Extract the URL and the query string as an object.
*
* @param url - The URL to parse.
*
* @example
*
* queryString.parseUrl('https://foo.bar?foo=bar');
* //=> {url: 'https://foo.bar', query: {foo: 'bar'}}
*/
export function parseUrl(url: string, options?: ParseOptions): ParsedUrl;

export interface StringifyOptions {
/**
* Strictly encode URI components with [`strict-uri-encode`](https://github.com/kevva/strict-uri-encode). It uses [`encodeURIComponent`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to `false`. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option.
*
* @default true
*/
readonly strict?: boolean;

/**
* [URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values.
*
* @default true
*/
readonly encode?: boolean;

/**
* Supports both `index` for an indexed array representation or `bracket` for a *bracketed* array representation.
*
* @default 'none'
*
* - `bracket`: stands for parsing correctly arrays with bracket representation on the query string, such as:
*
*
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the extra empty line here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VS Code only renders the following example as code if I add this extra empty line.

An alternative (which I dislike) would be to write it like this:

	 * - `bracket`: stands for parsing correctly arrays with bracket representation on the query string, such as:
	 * 
	 * ```js
  queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
  //=> foo: [1,2,3]
```
	 *

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. I had a feeling it was something like that.

* queryString.stringify({foo: [1,2,3]}, {arrayFormat: 'bracket'});
* // => foo[]=1&foo[]=2&foo[]=3
*
* - `index`: stands for parsing taking the index into account, such as:
*
*
* queryString.stringify({foo: [1,2,3]}, {arrayFormat: 'index'});
* // => foo[0]=1&foo[1]=2&foo[3]=3
*
* - `none`: is the **default** option and removes any bracket representation, such as:
*
*
* queryString.stringify({foo: [1,2,3]});
* // => foo=1&foo=2&foo=3
*/
readonly arrayFormat?: 'bracket' | 'index' | '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.
*
* @example
*
* const order = ['c', 'a', 'b'];
* queryString.stringify({ a: 1, b: 2, c: 3}, {
* sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
* });
* // => 'c=3&a=1&b=2'
*
* queryString.stringify({ b: 1, c: 2, a: 3}, {sort: false});
* // => 'b=1&c=2&a=3'
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
}

/**
* Stringify an object into a query string, sorting the keys.
*/
export function stringify(
object: {[key: string]: unknown},
options?: StringifyOptions
): string;

/**
* Extract a query string from a URL that can be passed into `.parse()`.
*/
export function extract(url: string): string;
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function encoderForArrayFormat(options) {
encode(value, options)
].join('');
};

case 'bracket':
return (key, value) => {
return value === null ? [encode(key, options), '[]'].join('') : [
Expand All @@ -27,6 +28,7 @@ function encoderForArrayFormat(options) {
encode(value, options)
].join('');
};

default:
return (key, value) => {
return value === null ? encode(key, options) : [
Expand Down Expand Up @@ -59,6 +61,7 @@ function parserForArrayFormat(options) {

accumulator[key][result[1]] = value;
};

case 'bracket':
return (key, value, accumulator) => {
result = /(\[\])$/.exec(key);
Expand All @@ -76,6 +79,7 @@ function parserForArrayFormat(options) {

accumulator[key] = [].concat(accumulator[key], value);
};

default:
return (key, value, accumulator) => {
if (accumulator[key] === undefined) {
Expand Down
68 changes: 68 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {expectType} from 'tsd-check';
import * as queryString from '.';

// Stringify
expectType<string>(
queryString.stringify({
str: 'bar',
strArray: ['baz'],
num: 123,
numArray: [456],
bool: true,
boolArray: [false]
})
);

expectType<string>(queryString.stringify({foo: 'bar'}, {strict: false}));
expectType<string>(queryString.stringify({foo: 'bar'}, {encode: false}));
expectType<string>(
queryString.stringify({foo: 'bar'}, {arrayFormat: 'bracket'})
);
expectType<string>(queryString.stringify({foo: 'bar'}, {arrayFormat: 'index'}));
expectType<string>(queryString.stringify({foo: 'bar'}, {arrayFormat: 'none'}));
expectType<string>(queryString.stringify({foo: 'bar'}, {sort: false}));
const order = ['c', 'a', 'b'];
expectType<string>(
queryString.stringify(
{foo: 'bar'},
{
sort: (itemLeft, itemRight) =>
order.indexOf(itemLeft) - order.indexOf(itemRight)
}
)
);

// Parse
expectType<queryString.ParsedQuery>(queryString.parse('?foo=bar'));

expectType<queryString.ParsedQuery>(
queryString.parse('?foo=bar', {decode: false})
);
expectType<queryString.ParsedQuery>(
queryString.parse('?foo=bar', {arrayFormat: 'bracket'})
);
expectType<queryString.ParsedQuery>(
queryString.parse('?foo=bar', {arrayFormat: 'index'})
);
expectType<queryString.ParsedQuery>(
queryString.parse('?foo=bar', {arrayFormat: 'none'})
);

// Parse URL
expectType<queryString.ParsedUrl>(queryString.parseUrl('?foo=bar'));

expectType<queryString.ParsedUrl>(
queryString.parseUrl('?foo=bar', {decode: false})
);
expectType<queryString.ParsedUrl>(
queryString.parseUrl('?foo=bar', {arrayFormat: 'bracket'})
);
expectType<queryString.ParsedUrl>(
queryString.parseUrl('?foo=bar', {arrayFormat: 'index'})
);
expectType<queryString.ParsedUrl>(
queryString.parseUrl('?foo=bar', {arrayFormat: 'none'})
);

// Extract
expectType<string>(queryString.extract('http://foo.bar/?abc=def&hij=klm'));
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"node": ">=6"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava && tsd-check"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"browser",
Expand All @@ -38,9 +39,10 @@
"strict-uri-encode": "^2.0.0"
},
"devDependencies": {
"ava": "^0.25.0",
"ava": "^1.2.1",
"deep-equal": "^1.0.1",
"fast-check": "^1.5.0",
"xo": "^0.23.0"
"tsd-check": "^0.3.0",
"xo": "^0.24.0"
}
}