Skip to content

Commit

Permalink
feat(urls): Add queryStringify utility
Browse files Browse the repository at this point in the history
  • Loading branch information
MorevM committed Jan 8, 2024
1 parent 6e1c4ab commit ba22a31
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './promises';
export * from './ranges';
export * from './strings';
export * from './types';
export * from './urls';
1 change: 1 addition & 0 deletions src/urls/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './query-stringify/query-stringify';
32 changes: 32 additions & 0 deletions src/urls/query-stringify/query-stringify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { queryStringify } from './query-stringify';

describe('query-stringify', () => {
it('Returns empty string for empty/invalid input', () => {
/* @ts-expect-error -- Edge case */
expect(queryStringify('')).toBe('');

expect(queryStringify([])).toBe('');
expect(queryStringify(undefined)).toBe('');
expect(queryStringify({})).toBe('');
});

it('Stringifies a simple object', () => {
expect(queryStringify({ a: 1, b: '2' })).toBe('a=1&b=2');
});

it('Encodes keys and values', () => {
expect(queryStringify({ 'foo+bar': 'foo+bar' })).toBe('foo%2Bbar=foo%2Bbar');
});

it('Stringifies an object containing nested keys', () => {
expect(queryStringify({ a: 1, b: '2', c: { a: { d: 1 } } })).toBe('a=1&b=2&c[a][d]=1');
});

it('Stringifies an arrays', () => {
expect(queryStringify({ a: 1, b: ['2', 3] })).toBe('a=1&b[0]=2&b[1]=3');
});

it('Respects `prefix` argument', () => {
expect(queryStringify({ a: 1, b: 2 }, 'data')).toBe('data[a]=1&data[b]=2');
});
});
26 changes: 26 additions & 0 deletions src/urls/query-stringify/query-stringify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { isPlainObject, isArray, isEmpty } from '../../guards';
import { tsObject } from '../../objects';
import type { PlainObject } from '../../types';

/**
* Stringify an object for use in a query string.
*
* @param object An object to stringify.
* @param prefix A prefix for root element.
*
* @returns Query string built from the object.
*/
export const queryStringify = (object: PlainObject | undefined, prefix?: string) => {
if (isEmpty(object)) return '';

return tsObject.entries(object).reduce<string[]>((acc, [key, value]) => {
const encodedKey = encodeURIComponent(key);
const pairKey = prefix ? `${prefix}[${encodedKey}]` : encodedKey;
const pair = isPlainObject(value) || isArray(value)
? queryStringify(value, pairKey)
: `${pairKey}=${encodeURIComponent(value)}`;

acc.push(pair);
return acc;
}, []).filter(Boolean).join('&');
};

0 comments on commit ba22a31

Please sign in to comment.