# picoquery

> A small library for parsing and serialisation query strings

Latest version **2.5.0** (published 2025-06-21) · MIT license · 0 weekly downloads

## Install

```sh
npm install picoquery
pnpm add picoquery
yarn add picoquery
bun add picoquery
```

## Health

**Score 60/100 (C)** — status: stable.

Positive: has types; esm support; no vulnerabilities; has provenance; high maintenance score; high quality score.

Warnings: low downloads.

Negative: stale.

## Facts

| | |
|---|---|
| Version | 2.5.0 |
| Published | 2025-06-21 |
| First published | 2024-06-20 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 35.6 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 108 |
| Author | James Garbutt |
| Maintainers | 43081j |
| Keywords | qs, querystring, query, query-string, url |

## Links

- npm: https://www.npmjs.com/package/picoquery
- Repository: https://github.com/43081j/picoquery
- Homepage: https://github.com/43081j/picoquery#readme
- Issues: https://github.com/43081j/picoquery/issues
- npm.io page: https://npm.io/package/picoquery

## Alternatives

- [gamedig](https://npm.io/package/gamedig.md) — 29.3K weekly downloads
- [join-monster](https://npm.io/package/join-monster.md) — 12.8K weekly downloads
- [masked](https://npm.io/package/masked.md) — 5.5K weekly downloads
- [@comunica/actor-query-process-explain-logical](https://npm.io/package/@comunica/actor-query-process-explain-logical.md) — 4.7K weekly downloads
- [@veracity/vui](https://npm.io/package/@veracity/vui.md) — 4.6K weekly downloads

## Recent versions

- 2.5.0 (latest) — 2025-06-21
- 1.5.0 (v1-stable) — 2025-06-22
- 1.1.0-beta.1 (v1-next) — 2024-06-23
- 1.1.0-beta.0 (next) — 2024-06-23
- 2.4.1 — 2025-06-17
- 2.4.0 — 2024-07-27
- 1.4.0 — 2024-07-27
- 2.3.1 — 2024-06-25
- 1.3.1 — 2024-06-25
- 2.3.0 — 2024-06-24
- 1.3.0 — 2024-06-24
- 2.2.0 — 2024-06-23
- 1.2.0 — 2024-06-23
- 1.1.0 — 2024-06-23
- 2.1.0 — 2024-06-23
- … 7 more at https://npm.io/package/picoquery/versions

## README

# picoquery

[![CI](https://github.com/43081j/picoquery/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/43081j/picoquery/actions/workflows/main.yml)
[![NPM version](https://img.shields.io/npm/v/picoquery.svg?style=flat)](https://www.npmjs.com/package/picoquery)

A lightweight query string parser/stringifier with support for nesting and some
configurability.

Built on top of [fast-querystring](https://github.com/anonrig/fast-querystring).

## Install

```sh
npm i -S picoquery
```

### CommonJS vs ESM

`2.x` and above are _ESM only_ (i.e. your project will need `type: "module"`
in the `package.json`).

If you cannot yet move to ESM, you may continue to use `1.x` which will still
receive non-breaking changes, backported from `main`.

## Usage

Parsing a query string:

```ts
import {parse} from 'picoquery';

parse('foo.bar=abc&baz=def');

/*
  {
    foo: {
      bar: 'abc'
    },
    baz: 'def'
  }
*/
```

Stringifying an object:

```ts
import {stringify} from 'picoquery';

stringify({
  foo: {
    bar: 123
  }
});

/*
foo.bar=123
*/
```

## API

### `stringify(object[, options])`

Converts the given object into a query string, optionally with configured
options.

### `parse(str[, options])`

Parses the given query string into an object, optionally with configured
options.

## Options

### Default options

The default options are as follows:

```ts
{
  nesting: true,
  nestingSyntax: 'dot',
  arrayRepeat: false,
  arrayRepeatSyntax: 'repeat',
  delimiter: '&'
}
```

### `nesting`

When true, nested objects are supported.

For example, when parsing:

```ts
parse('foo.bar=baz', {nesting: true});

// {foo: {bar: 'baz'}}
```

When stringifying:

```ts
stringify({foo: {bar: 'baz'}}, {nesting: true});

// foo.bar=baz
```

This also results in arrays being supported:

```ts
parse('foo.0=bar', {nesting: true});
// {foo: ['bar']}

stringify({foo: ['bar']}, {nesting: true});
// foo.0=bar
```

### `nestingSyntax`

Sets which style of nesting syntax should be used. The choices are:

- `dot` (e.g. `foo.bar=baz`)
- `index` (e.g. `foo[bar]=baz`)
- `js` (e.g. `foo.bar[0]=baz`, i.e. arrays are indexed and properties are
dotted or indexed)

### `arrayRepeat`

If `true`, this will treat repeated keys as arrays.

For example:

```ts
parse('foo=x&foo=y', {arrayRepeat: true});
// {foo: ['x', 'y']}

stringify({foo: ['x', 'y']}, {arrayRepeat: true});
// foo=x&foo=y
```

### `arrayRepeatSyntax`

Sets which style of array repetition syntax should be used. The choices are:

- `bracket` (e.g. `foo[]=x&foo[]=y`)
- `repeat` (e.g. `foo=x&foo=y`)

### `delimiter`

Sets the delimiter to be used instead of `&`.

For example:

```ts
parse('foo=x;bar=y', {delimiter: ';'});
// {foo: 'x', bar: 'y'}

stringify({foo: 'x', bar: 'y'}, {delimiter: ';'});
// foo=x;bar=y
```

### `valueDeserializer`

Can be set to a function which will be used to deserialize each value during
parsing.

It will be called with the `value` and the already deserialized
`key` (i.e. `(value: string, key: PropertyKey) => *`).

For example:

```ts
parse('foo=300', {
  valueDeserializer: (value) => {
    const asNum = Number(value);
    return Number.isNaN(asNum) ? value : asNum;
  }
});

// {foo: 300}
```

### `keyDeserializer`

Can be set to a function which will be used to deserialize each key during
parsing.

It will be called with the `key` from the query string
(i.e. `(key) => PropertyKey`).

For example:

```ts
parse('300=foo', {
  keyDeserializer: (key) => {
    const asNum = Number(key);
    return Number.isNaN(asNum) ? key : asNum;
  }
});

// {300: 'foo'}
```

### `shouldSerializeObject`

Can be set to a function which determines if an _object-like_ value should be
serialized instead of being treated as a nested object.

**All non-object primitives will always be serialized.**

For example:

```
// Assuming `StringifableObject` returns its constructor value when `toString`
// is called.
stringify({
  foo: new StringifiableObject('test')
}, {
  shouldSerializeObject(val) {
    return val instanceof StringifableObject;
  },
  valueSerializer: (value) => {
    return String(value);
  }
});

// foo=test
```

If you want to fall back to the default logic, you can import the default
function:

```
import {defaultShouldSerializeObject, stringify} from 'picoquery';

stringify({
  foo: new StringifiableObject('test')
}, {
  shouldSerializeObject(val) {
    if (val instanceof StringifableObject) {
      return true;
    }
    return defaultShouldSerializeObject(val);
  }
});
```

### `valueSerializer`

Can be set to a function which will be used to serialize each value during
stringifying.

It will be called with the `value` and the `key`
(i.e. `(value: unknown, key: PropertyKey) => string`).

For example:

```ts
stringify({foo: 'bar'}, {
  valueSerializer: (val) => String(val) + String(val)
});

// foo=barbar
```

**Note** that you can import the default serializer if you only want to handle
some cases.

For example:

```ts
import {defaultValueSerializer, stringify} from 'picoquery';

stringify({foo: 'bar'}, {
  valueSerializer: (val, key) => {
    if (val instanceof Date) {
      return val.toISOString();
    }

    // Call the original serializer otherwise
    return defaultValueSerializer(val, key);
  }
});
```

## Benchmarks

**IMPORTANT**: there are a few things to take into account with these
benchmarks:

- `fast-querystring` is not capable of parsing or stringifying nested objects,
so the results are incomparible (but here as reference)
- all libraries have their own level of configurability. It will be possible
to increase perf in each of them by disabling various features, but these are
just the 'happy path'

### Parse

```
Benchmark: Basic (no nesting)
┌─────────┬─────────────────────────────────┬─────────────┬────────────────────┬──────────┬─────────┐
│ (index) │ Task Name                       │ ops/sec     │ Average Time (ns)  │ Margin   │ Samples │
├─────────┼─────────────────────────────────┼─────────────┼────────────────────┼──────────┼─────────┤
│ 0       │ 'picoquery'                     │ '2,393,539' │ 417.79130492907393 │ '±0.42%' │ 1196770 │
│ 1       │ 'qs'                            │ '382,933'   │ 2611.4212945313157 │ '±1.04%' │ 191467  │
│ 2       │ 'fast-querystring (no nesting)' │ '2,633,148' │ 379.77342802356833 │ '±1.58%' │ 1316575 │
└─────────┴─────────────────────────────────┴─────────────┴────────────────────┴──────────┴─────────┘
Benchmark: Dot-syntax nesting
┌─────────┬─────────────────────────────────┬─────────────┬───────────────────┬──────────┬─────────┐
│ (index) │ Task Name                       │ ops/sec     │ Average Time (ns) │ Margin   │ Samples │
├─────────┼─────────────────────────────────┼─────────────┼───────────────────┼──────────┼─────────┤
│ 0       │ 'picoquery'                     │ '1,406,039' │ 711.2176054736389 │ '±0.56%' │ 703020  │
│ 1       │ 'qs'                            │ '205,611'   │ 4863.536155478235 │ '±0.98%' │ 102806  │
│ 2       │ 'fast-querystring (no nesting)' │ '2,588,560' │ 386.3151031345139 │ '±0.66%' │ 1294281 │
└─────────┴─────────────────────────────────┴─────────────┴───────────────────┴──────────┴─────────┘
```

### Stringify

```
Benchmark: Basic (no nesting)
┌─────────┬─────────────────────────────────┬─────────────┬────────────────────┬──────────┬─────────┐
│ (index) │ Task Name                       │ ops/sec     │ Average Time (ns)  │ Margin   │ Samples │
├─────────┼─────────────────────────────────┼─────────────┼────────────────────┼──────────┼─────────┤
│ 0       │ 'picoquery'                     │ '4,163,092' │ 240.20605684139227 │ '±0.16%' │ 2081547 │
│ 1       │ 'qs'                            │ '843,630'   │ 1185.352608720127  │ '±0.76%' │ 421816  │
│ 2       │ 'fast-querystring (no nesting)' │ '3,795,565' │ 263.46536774751036 │ '±0.24%' │ 1897783 │
└─────────┴─────────────────────────────────┴─────────────┴────────────────────┴──────────┴─────────┘
Benchmark: Dot-syntax nesting
┌─────────┬─────────────────────────────────┬─────────────┬────────────────────┬──────────┬─────────┐
│ (index) │ Task Name                       │ ops/sec     │ Average Time (ns)  │ Margin   │ Samples │
├─────────┼─────────────────────────────────┼─────────────┼────────────────────┼──────────┼─────────┤
│ 0       │ 'picoquery'                     │ '1,768,770' │ 565.3644818387182  │ '±1.08%' │ 884387  │
│ 1       │ 'qs'                            │ '332,254'   │ 3009.743667534287  │ '±1.38%' │ 166128  │
│ 2       │ 'fast-querystring (no nesting)' │ '7,227,031' │ 138.36940410118828 │ '±1.15%' │ 3613517 │
└─────────┴─────────────────────────────────┴─────────────┴────────────────────┴──────────┴─────────┘
```

## License

MIT

---
_Source: https://npm.io/package/picoquery · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
