qs nested query strings: working versions (tested 2026-10-15)
Parse and stringify nested query strings with qs, covering nested objects, bracket/index/repeat/comma array formats, depth limits and a typed filter parser offline.
passed8 passed · 0 failed2026-10-15 22:58 UTC- Status
- passed
- Last passed
- 2026-10-15 22:58 UTC
- Tests
- 8 / 8
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
qs@6.16.1@types/qs@6.15.1@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38820570375
The code
Exactly the files that passed. Your agent can pull them with get_recipe("qs-nested-query-strings") (install), or see them on GitHub.
package.json
{
"name": "qs-nested-query-strings",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {"qs":"6.16.1"},
"devDependencies": {"@types/qs":"6.15.1","@types/node":"26.6.5","tsx":"4.23.15"}
}
src/query.ts
import { parse, stringify, type IParseOptions, type ParsedQs, type IStringifyOptions } from "qs";
export const DEFAULT_DEPTH = 5;
export function parseNested(input: string, options: IParseOptions = {}): ParsedQs {
return parse(input, { depth: DEFAULT_DEPTH, ...options }) as ParsedQs;
}
export function stringifyNested(value: Record<string, unknown>, options: IStringifyOptions = {}): string {
return stringify(value, { depth: DEFAULT_DEPTH, ...options });
}
export type Filter = {
q?: string;
tags: string[];
price?: { min?: number; max?: number };
inStock?: boolean;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function toNumber(value: unknown): number | undefined {
if (typeof value === "string" && value.trim() !== "") {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return undefined;
}
export function parseFilter(input: string): Filter {
const raw = parse(input, { depth: DEFAULT_DEPTH });
const filter: Filter = { tags: [] };
if (typeof raw.q === "string") filter.q = raw.q;
const tags = raw.tags;
if (typeof tags === "string") {
filter.tags = [tags];
} else if (Array.isArray(tags)) {
filter.tags = tags.filter((tag): tag is string => typeof tag === "string");
}
const price = raw.price;
if (isRecord(price)) {
const min = toNumber(price.min);
const max = toNumber(price.max);
if (min !== undefined || max !== undefined) {
filter.price = {};
if (min !== undefined) filter.price.min = min;
if (max !== undefined) filter.price.max = max;
}
}
const inStock = raw.inStock;
if (inStock === "true" || inStock === "1") {
filter.inStock = true;
} else if (inStock === "false" || inStock === "0") {
filter.inStock = false;
}
return filter;
}
export function toQuery(filter: Filter, options: IStringifyOptions = {}): string {
const payload: Record<string, unknown> = {};
if (filter.q !== undefined) payload.q = filter.q;
if (filter.tags.length > 0) payload.tags = filter.tags;
if (filter.price !== undefined) payload.price = filter.price;
if (filter.inStock !== undefined) payload.inStock = filter.inStock ? "true" : "false";
return stringify(payload, { skipNulls: true, ...options });
}
test/query.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { parseNested, stringifyNested, parseFilter, toQuery } from "../src/query.ts";
test("parses nested objects and indexed arrays", () => {
const parsed = parseNested("user[name]=ada&user[roles][0]=admin&user[roles][1]=dev");
assert.deepEqual(parsed, { user: { name: "ada", roles: ["admin", "dev"] } });
});
test("parses bracket and repeat array formats", () => {
assert.deepEqual(parseNested("tags[]=a&tags[]=b"), { tags: ["a", "b"] });
assert.deepEqual(parseNested("tags=a&tags=b"), { tags: ["a", "b"] });
});
test("parses comma separated arrays", () => {
assert.deepEqual(parseNested("tags=a,b,c", { comma: true }), { tags: ["a", "b", "c"] });
});
test("stringifies arrays in each format", () => {
const value = { tags: ["a", "b"] };
assert.equal(stringifyNested(value, { arrayFormat: "indices", encode: false }), "tags[0]=a&tags[1]=b");
assert.equal(stringifyNested(value, { arrayFormat: "brackets", encode: false }), "tags[]=a&tags[]=b");
assert.equal(stringifyNested(value, { arrayFormat: "repeat", encode: false }), "tags=a&tags=b");
assert.equal(stringifyNested(value, { arrayFormat: "comma", encode: false }), "tags=a,b");
});
test("enforces the depth limit while parsing", () => {
assert.deepEqual(parseNested("a[b][c][d][e][f][g][h][i]=j", { depth: 1 }), {
a: { b: { "[c][d][e][f][g][h][i]": "j" } },
});
});
test("parses a typed filter", () => {
const filter = parseFilter("q=phone&tags[]=mobile&tags[]=sale&price[min]=10&price[max]=99&inStock=true");
assert.deepEqual(filter, {
q: "phone",
tags: ["mobile", "sale"],
price: { min: 10, max: 99 },
inStock: true,
});
});
test("ignores non numeric and non boolean values", () => {
assert.deepEqual(parseFilter("price[min]=cheap&inStock=maybe"), { tags: [] });
});
test("round-trips a filter through toQuery and parseFilter", () => {
const filter = { q: "phone", tags: ["sale", "mobile"], price: { min: 10 }, inStock: false };
const query = toQuery(filter, { encode: false });
assert.equal(query, "q=phone&tags[0]=sale&tags[1]=mobile&price[min]=10&inStock=false");
assert.deepEqual(parseFilter(query), filter);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.