Quick answer: Query parameters that are arrays or nested objects are one of the most common silent contract mismatches, because the OpenAPI name and type say nothing about how the value is serialized. The same array can be sent as repeated keys, comma-joined text, or bracketed indices depending on the style and explode settings, and a server that expects one form will misread or ignore the others. The fix is to declare the serialization explicitly in the spec, default to the simplest form a client can reliably produce, and test the exact encoded query string rather than the in-memory object. If you have never set style or explode, your API is relying on defaults that generators and hand-written clients do not always share.
The reason this breaks so often is that both sides look correct in isolation. The client holds an array and attaches it to a query parameter; the server declares a parameter with an array schema. The mismatch lives entirely in the encoding step between them, which is invisible until a request is inspected on the wire. A server that expects repeated keys and receives a comma-joined string will treat the whole string as one element rather than several, and the failure often appears as a filter that quietly returns the wrong set.
The array encoding forms
For a parameter named tag with values api and rest, the common encodings produce visibly different query strings.
| Style / explode | Encoded query string | Typical default |
|---|---|---|
| form, explode=true | ?tag=api&tag=rest |
Query default |
| form, explode=false | ?tag=api,rest |
Explicit |
| spaceDelimited | ?tag=api%20rest |
Explicit |
| pipeDelimited | `?tag=api\ | rest` |
| bracketed (non-standard) | ?tag[]=api&tag[]=rest |
Framework convention |
The bracketed form is worth flagging because it is not an OpenAPI defined style, yet several server frameworks expect it by default. A generated client that follows the standard form and a framework that expects brackets will disagree without either side being "wrong" against its own conventions.
Declare style and explode explicitly
Do not rely on the default for any array or object parameter. Write the encoding in the spec so every generated client and every reader sees the same contract, and choose the form your server framework actually parses without extra configuration.
For nested objects, deepObject is the only OpenAPI defined mechanism, and it carries an important caveat: support for arrays and deeper nesting inside the object is uneven across generators. The safe practice is to keep deep objects shallow and validate what your chosen clients actually emit.
Test the encoded string, not the object
Asserting the in-memory parameter object proves nothing about serialization. Build the request and inspect the literal query string, or assert against the parsed result on the server after decoding, so the encoding contract is actually verified.
Reading the values back through a standard URL parser confirms the round trip rather than merely checking that the object was passed in. This catches the exact class of bug where the client and server assume different encodings.
Keep generated clients aligned with the framework
A local-first workflow that generates clients from the spec will honor the declared style and explode automatically, which is the main reason to declare them rather than depend on framework defaults. When the server framework needs a non-standard bracket form, either configure the framework to accept the standard style or document the convention prominently and confirm the generator supports it, rather than leaving the gap implicit.
The larger principle is that a contract is not fully described until the wire encoding is described. Arrays and objects in the query string need explicit styles, shallow and well-supported structures, and tests against the literal encoded string. Anything less is a handshake that the two sides can fail without noticing.
Frequently asked questions
What happens if I omit style and explode?
OpenAPI applies a default, form with explode true for query parameters, but hand-written clients and some server frameworks may use a different convention. Declaring it explicitly removes the dependency on everyone implementing the same default.
Can I use the bracket notation in OpenAPI?
The bracket form is not a defined OpenAPI style, though frameworks commonly expect it. Prefer the standard form and configure the framework to accept it, or document the convention and verify your generator can emit it.
Is deepObject safe for complex nesting?
Support is inconsistent for arrays and nested objects within a deep object. Keep the structure shallow, and if you need richer filtering, consider a POST with a request body instead of encoding it in the query string.
When should I move complex filters out of the query?
When the filter includes nested arrays, multiple levels of objects, or large value sets, a POST with a typed body is more reliable and easier to validate than forcing everything into query serialization.
How do generated clients help here?
They read the declared style and explode values and encode accordingly, so the client cannot silently drift from the server contract. The spec becomes the single place where the encoding is decided.
This is part of the OpenAPI Guide — start there for the full discipline of designing contracts that survive real clients.
Top comments (0)