Query string semantics guide

Query String Encoding and Repeated Parameters Explained

Query strings combine structural separators with form-style percent encoding. Decode only after the parameter boundaries are understood, preserve repeated keys and empty values instead of collapsing them, and use an explicit convention when nested data must travel inside one value. VetaTool follows browser URLSearchParams behavior so Parse and Build modes stay predictable in both directions.

At a glance

Quick answer

  • A raw ampersand starts another parameter, while %26 keeps an ampersand inside one decoded value; encode embedded URLs before placing them inside another query string.
  • URLSearchParams uses application/x-www-form-urlencoded behavior: + represents a space during parsing, while %2B represents a literal plus sign, and Build mode writes spaces as +.
  • Repeated keys are meaningful and ordered. VetaTool preserves the first value and later values as a JSON array instead of silently overwriting them.
  • Empty strings, nulls, primitive arrays, and omitted empty arrays have different Build-mode outcomes, so keep the receiving API's parameter contract explicit.

Separate query syntax from data before decoding

The question mark starts the query component of a URL, ampersands separate name-value pairs, and an equals sign separates a parameter name from its value. Those characters can also be ordinary data, but only after they are percent-encoded inside the component that owns them.

VetaTool accepts a complete URL, a relative URL containing a question mark, or a raw query string. It extracts the query portion first and then lets URLSearchParams decode each key and value. That ordering matters because decoding an encoded ampersand too early would turn data back into a separator before the parameter boundaries are known.

Raw ampersand creates another parameter

?note=fish&chips&format=json

Encoded ampersand stays inside one value

?note=fish%26chips&format=json

Treat plus signs and percent escapes as form-style encoding

URLSearchParams follows application/x-www-form-urlencoded rules rather than treating every plus sign as a literal plus character. A raw + decodes to a space, while %2B decodes to +. Percent-encoded UTF-8 sequences are decoded in keys and values as part of the same parsing step.

Build mode uses URLSearchParams.toString(), so spaces are serialized as + and characters that need escaping are percent-encoded. If the receiving system distinguishes a literal plus from a space, encode the plus as %2B before treating the text as a query value.

Space versus literal plus

?term=C%2B%2B+guide

Parsed value

{
  "term": "C++ guide"
}

Preserve repeated keys, empty values, and flags

Repeated parameters are not automatically duplicates to discard. APIs commonly use repeated keys for filters, tags, or multi-select values. VetaTool records the first value as a string and converts that key to an ordered JSON array when another value with the same name appears.

An explicit empty value such as name= remains an empty string. A flag-style parameter such as debug is also parsed by URLSearchParams as the key debug with an empty-string value. A missing key is different because it never appears in the parsed object at all.

Repeated and empty parameters

?tag=json&tag=tools&debug&name=

Parsed JSON

{
  "tag": [
    "json",
    "tools"
  ],
  "debug": "",
  "name": ""
}

Build repeated parameters from JSON arrays deliberately

Build mode requires a top-level JSON object. A primitive value becomes one query parameter, while an array of primitive values becomes the same key repeated once per array item in its original order. Nested objects are rejected because bracket notation, dotted keys, JSON text, and other nested-query conventions are application-specific rather than interchangeable.

null is emitted as an explicit empty value, just like an empty string, while an empty array emits no parameter pairs because there are no array items to append. Booleans and finite safe numbers are converted to their string forms. Unsafe integers are rejected before serialization so large identifiers are not silently rounded by JavaScript number handling.

Build input

{
  "tag": ["json", "tools"],
  "debug": true,
  "empty": null,
  "skip": []
}

Built query string

tag=json&tag=tools&debug=true&empty=

Encode embedded URLs and ignore fragments at the right boundary

A fragment beginning with # is outside the query component, so VetaTool ignores it when parsing a complete or relative URL. Text inside the fragment should not be treated as another query parameter even when it contains equals signs or ampersands.

An embedded URL is the opposite case: its inner ?, &, and = characters belong to data inside an outer parameter value. Encode that nested URL as one component before appending it, otherwise the outer parser can mistake the inner ampersands for additional top-level parameters.

Encoded redirect URL

?next=https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Djson%26page%3D2

Use a repeatable query-string diagnosis workflow

Start with the smallest URL or raw query that still shows the unexpected behavior. Mark the outer separators first, then inspect percent escapes, repeated names, empty values, and embedded components. Only after the parsed key-value model is clear should you decide whether the receiving application expects arrays, flags, bracket notation, or another convention.

  • Separate the query component from the path and fragment before interpreting parameters.
  • Check raw &, =, +, and % characters to decide which are syntax and which are intended data.
  • Preserve repeated keys and empty values while comparing the parsed result with the API contract.
  • When building, use only the primitive or primitive-array representation that the receiver documents.
  • Encode embedded URLs or other structured values as one component before placing them inside an outer query string.