Utilities

RezoURLSearchParams

RezoURLSearchParams provides support for nested objects and arrays in URL query strings. It supports three nesting modes — json, brackets, and dots — and can reconstruct the original nested structure from flat query strings.

import { RezoURLSearchParams } from 'rezo';

Nested Modes

The nestedKeys option controls how nested objects and arrays are serialized:

json Mode (default)

Nested values are JSON-stringified as string values. Simple and universal, but the server must parse JSON strings from query parameters.

const params = new RezoURLSearchParams({
  name: 'Alice',
  tags: ['admin', 'user'],
  address: { city: 'NYC', zip: '10001' },
}, { nestedKeys: 'json' });

params.toString();
// name=Alice&tags=%5B%22admin%22%2C%22user%22%5D&address=%7B%22city%22%3A%22NYC%22%2C%22zip%22%3A%2210001%22%7D
// Decoded: name=Alice&tags=["admin","user"]&address={"city":"NYC","zip":"10001"}

brackets Mode

Uses bracket notation compatible with PHP, Rails, Express/qs, and most server frameworks:

const params = new RezoURLSearchParams({
  user: {
    name: 'Alice',
    roles: ['admin', 'editor'],
    address: { city: 'NYC' },
  },
}, { nestedKeys: 'brackets' });

params.toString();
// user[name]=Alice&user[roles][0]=admin&user[roles][1]=editor&user[address][city]=NYC

Note: toString() in brackets mode automatically decodes %5B and %5D back to literal [ and ], matching the convention expected by standard form parsers.

dots Mode

Uses dot notation for nesting:

const params = new RezoURLSearchParams({
  user: {
    name: 'Alice',
    roles: ['admin', 'editor'],
  },
}, { nestedKeys: 'dots' });

params.toString();
// user.name=Alice&user.roles.0=admin&user.roles.1=editor

Constructor

Accepts all standard URLSearchParams initializers plus nested objects:

// From nested object
const params = new RezoURLSearchParams({ key: 'value' });

// From string
const params = new RezoURLSearchParams('key=value&other=123');

// From array of pairs
const params = new RezoURLSearchParams([['key', 'value']]);

// From another URLSearchParams
const params = new RezoURLSearchParams(new URLSearchParams('a=1'));

// From another RezoURLSearchParams (inherits nested mode)
const params = new RezoURLSearchParams(existing);

// With explicit nested mode
const params = new RezoURLSearchParams(data, { nestedKeys: 'brackets' });

appendObject(obj, prefix?)

Append all keys from a nested object. Use prefix for sub-objects:

const params = new RezoURLSearchParams(undefined, { nestedKeys: 'brackets' });

params.appendObject({
  filter: {
    status: 'active',
    tags: ['important', 'urgent'],
  },
});

params.toString();
// filter[status]=active&filter[tags][0]=important&filter[tags][1]=urgent

Multiple calls to appendObject accumulate values:

params.appendObject({ page: '1' });
params.appendObject({ page: '2' });
params.toString();
// page=1&page=2

setObject(obj, prefix?)

Like appendObject, but replaces existing values with the same prefix:

const params = new RezoURLSearchParams(undefined, { nestedKeys: 'brackets' });

params.setObject({ filter: { status: 'draft' } });
params.toString(); // filter[status]=draft

params.setObject({ filter: { status: 'published' } });
params.toString(); // filter[status]=published  (replaced, not duplicated)

When called without a prefix, setObject clears all existing parameters first.

toFlatObject()

Returns a plain object with keys as-is (no nested reconstruction):

const params = new RezoURLSearchParams(undefined, { nestedKeys: 'brackets' });
params.appendObject({ user: { name: 'Alice', age: 30 } });

params.toFlatObject();
// { 'user[name]': 'Alice', 'user[age]': '30' }

toObject()

Reconstructs the original nested structure by parsing bracket-notation keys and JSON-encoded values:

// From brackets
const params = new RezoURLSearchParams('user[name]=Alice&user[roles][0]=admin&user[roles][1]=editor');
params.toObject();
// { user: { name: 'Alice', roles: ['admin', 'editor'] } }

// From JSON mode
const params = new RezoURLSearchParams('tags=["a","b"]&config={"debug":true}');
params.toObject();
// { tags: ['a', 'b'], config: { debug: true } }

// Simple key-value pairs pass through
const params = new RezoURLSearchParams('name=Alice&page=1');
params.toObject();
// { name: 'Alice', page: '1' }

Array indices in bracket keys are detected automatically ([0], [1], etc.) and produce arrays instead of objects.

fromFlat(obj)

Static factory that creates a RezoURLSearchParams from a flat Record<string, string>:

const params = RezoURLSearchParams.fromFlat({
  'user[name]': 'Alice',
  'user[email]': 'alice@example.com',
});

params.toString();
// user%5Bname%5D=Alice&user%5Bemail%5D=alice%40example.com

Type Handling

RezoURLSearchParams handles these value types:

TypeSerialization
stringUsed as-is
numberConverted via String()
booleanConverted via String()
DateConverted via .toISOString()
null / undefinedSkipped (not appended)
ArrayIndexed keys (brackets/dots) or JSON (json mode)
ObjectNested keys (brackets/dots) or JSON (json mode)

Usage with Rezo

import rezo from 'rezo';
import { RezoURLSearchParams } from 'rezo';

// As query parameters
const params = new RezoURLSearchParams({
  filter: { status: 'active', category: 'tech' },
  sort: '-date',
  page: 1,
}, { nestedKeys: 'brackets' });

const response = await rezo.get('https://api.example.com/posts', {
  params,
});
// GET https://api.example.com/posts?filter[status]=active&filter[category]=tech&sort=-date&page=1

// As form body
const response = await rezo.post('https://api.example.com/search', params.toString(), {
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
});