Query String Parser / JSON Converter Guide
What is a query string parser?
A query string parser reads the parameter portion of a URL and turns it into a data structure your code can work with. Given ?q=logs&page=2&tag=api&tag=debug, it splits on &, separates keys from values, decodes escaped characters, and decides what to do with repeated keys. The result might be a map of strings, or it might treat repeated parameters as arrays.
That sounds simple until you move past the happy path. Query strings are not JSON. They are a flat transport format made of text. There is no universal standard for arrays, nested objects, empty values, or type coercion. One URL params parser might interpret tag=js&tag=ts as {"tag":["js","ts"]}. Another might keep only "js" or only "ts". The format itself allows the ambiguity.
This Toolzy.dev page helps with both directions: query string to JSON for inspection and query params to JSON debugging, plus JSON to query string when you need to generate links, test requests, or redirect URLs. Everything runs locally in the browser, which is useful when the input contains private query values, auth state, or production URLs you do not want to paste into a server-backed tool.
How to use this tool
- Paste either a raw query string, a full URL, or a JSON object into the input. Full URLs are parsed from the query portion after
?. - Choose whether you want query string to JSON or JSON to query string conversion.
- Check the output for repeated keys, empty values, and encoded characters like
%2For%2B. - Copy the result into your app, test suite, docs, or debugging notes.
For best results, keep JSON input flat unless you already know which nesting convention your backend expects.
Common use cases
- Inspect OAuth callback URLs with long
state,scope, andredirect_urivalues - Turn copied browser location strings into JSON fixtures for tests
- Generate clean query strings from UI filter state before wiring up routing
- Compare how a backend parses repeated keys like
role=admin&role=editor - Debug analytics links with
utm_*, campaign IDs, and encoded landing page paths - Check whether a literal plus sign should be
+or%2Bbefore shipping a link
Query string to JSON vs JSON to query string
Query string to JSON is mainly about visibility. You want to see what the URL actually says after decoding. For example:
name=Ada+Lovelace&tag=math&tag=logic
may parse to:
{"name":"Ada Lovelace","tag":["math","logic"]}
JSON to query string is mainly about generating a URL-safe representation of known state. A flat object like {"name":"Ada Lovelace","page":"1"} converts cleanly to name=Ada%20Lovelace&page=1. But the moment your JSON includes nested objects, mixed types, or arrays, you need a convention.
That is the main thing to remember with any query string parser or query string to JSON converter: you are converting between two formats with different expressive power. Flat state is straightforward. Rich state needs rules.
Repeated keys and arrays
Repeated keys are common and valid:
tag=js&tag=ts&tag=astro
The problem is interpretation, not syntax. Browsers allow repeated keys. Backends allow repeated keys. But not every parser exposes them the same way.
In the browser, URLSearchParams behaves like this:
const params = new URLSearchParams("tag=js&tag=ts");
params.get("tag");
// "js"
params.getAll("tag");
// ["js", "ts"]
That means a naive implementation that only uses get() can silently drop values. If you are testing a filter UI, search page, or multi-select routing state, repeated keys deserve a second look.
Arrays have no single canonical query string format. Common conventions include:
- Repeated keys:
tag=js&tag=ts - Bracket notation:
tag[]=js&tag[]=ts - Indexed keys:
tag[0]=js&tag[1]=ts - Delimited strings:
tag=js,ts
All of these are seen in production systems. None of them is universally correct.
URLSearchParams behavior and browser rules
URLSearchParams is convenient because it matches browser behavior closely, but it is intentionally narrow. It models query parameters as string pairs, not rich typed data. If you do this:
const params = new URLSearchParams();
params.set("page", String(2));
params.set("draft", String(false));
params.toString();
// "page=2&draft=false"
You get a valid result, but you do not get automatic type recovery later. When parsed back, both values are strings unless your application explicitly coerces them.
URLSearchParams also preserves insertion order and supports duplicate keys, which is useful for round-trip testing. But it does not understand nested objects. If you pass a complex JSON object into a generic serializer, the nested part often becomes "[object Object]" unless you flatten it first.
+ vs %20, decoding, and encoding rules
Spaces are where many debugging sessions go sideways. In many query contexts, especially application/x-www-form-urlencoded, a plus sign is interpreted as a space. That means these may be treated similarly by a parser:
q=hello+worldq=hello%20world
But + is not always meant to be a space semantically. If the literal value is C++, it should usually be encoded as C%2B%2B. Otherwise a decoder may read it as C .
Percent-encoding rules matter for other characters too:
%26represents&%3Drepresents=%2Frepresents/%2Brepresents+
If a query string contains malformed escapes such as %E0%A4 with missing bytes, decoding can fail or produce unexpected output depending on the implementation.
Flat vs nested structures
Flat JSON maps well to query parameters:
{"sort":"desc","page":"2","debug":"true"}
becomes:
sort=desc&page=2&debug=true
Nested JSON does not:
{
"filters": {
"status": "active",
"owner": "me"
},
"tags": ["js", "astro"]
}
Different stacks serialize that structure in different ways:
filters[status]=active&filters[owner]=me&tags[]=js&tags[]=astrofilters.status=active&filters.owner=me&tag=js&tag=astrofilters=%7B%22status%22%3A%22active%22%2C%22owner%22%3A%22me%22%7D&tags=js,astro
This is why the safest claim for a JSON to query string tool is that it handles flat objects well and makes complex cases visible, not magically standardized.
Round-trip caveats
Round-tripping means converting query string to JSON and then back again, or the reverse, without changing meaning. That works reliably for simple data like:
page=2&sort=asc&debug=false
It gets unreliable when any of these are true:
- Repeated keys might collapse if the parser keeps only one value
- Empty keys and key-only params may normalize differently
- Nested objects need a serializer-specific convention
- Numbers and booleans may come back as strings
- Key order may change, even if meaning does not
If exact round-trip fidelity matters for your app, define the serialization rules in one place and use the same encoder/decoder pair on both sides.
Troubleshooting
Why did my query string parser drop one of the repeated values? — The parser is probably using single-value lookup semantics like URLSearchParams.get() or a framework helper that keeps only the last or first value. Use an API that preserves duplicates.
Why does query string to JSON show strings instead of numbers or booleans? — Query parameters are text by default. Type coercion is application logic, not part of the query string format itself.
Why does JSON to query string break on nested objects? — Nested JSON needs a convention such as bracket notation, dotted keys, or a JSON-encoded parameter value. Without that rule, there is no universal output.
Why did + become a space in my output? — In many query-string decoding paths, + is treated as a space. Encode a literal plus sign as %2B.
Why does my backend parse arrays differently from this tool? — Array handling is not standardized across frameworks. Some expect repeated keys, some expect [] suffixes, and some expect comma-separated values.
Why doesn't the converted output match the original URL exactly? — Equivalent query strings can differ in key order, encoding style, or array notation. The meaning may be preserved even if the literal string changes.
Why do keys like toString or __proto__ work here? — The parser stores query keys in a prototype-less object, so property-like names are treated as ordinary keys instead of colliding with JavaScript object internals.