OpenAPI Query Parameter Serialization#
Kubb preserves OpenAPI style, explode, and additionalProperties metadata through its entire pipeline — from spec parsing into a spec-neutral AST, through code generation, to runtime serializers in generated client code. This end-to-end flow ensures generated clients serialize array and object parameters exactly as the OpenAPI spec declares.
Pipeline Overview#
OpenAPI Spec
│ style / explode / additionalProperties
▼
adapter-oas: parseParameter() ← reads raw spec fields
│ ParameterNode { style?, explode? }
▼
AST: ParameterNode ← spec-neutral representation
│
▼
Client generator: buildStyles() ← emits styles literal per operation
│ styles: { query: { param: { style, explode } } }
▼
Generated client function ← carries styles at call site
│
▼
Runtime: defaultQuerySerializer() ← applies style/explode at request time
1. AST: ParameterNode#
ParameterNode in @kubb/ast carries two optional serialization fields:
style?: ParameterStyle— one ofmatrix | label | form | simple | spaceDelimited | pipeDelimited | deepObjectexplode?: boolean— controls array/object expansion
Both fields are absent when the spec omits them, intentionally leaving consumers to apply per-location defaults rather than hard-coding OpenAPI assumptions into the AST.
2. Parsing: adapter-oas#
parseParameter() in @kubb/adapter-oas reads style and explode from the raw parameter object and sets them on the ParameterNode only when the spec provides them:
const style = param['style'] as ast.ParameterStyle | undefined
const explode = param['explode'] as boolean | undefined
// ...
...(style !== undefined ? { style } : {}),
...(explode !== undefined ? { explode } : {}),
This was introduced in PR #3676 specifically to preserve per-parameter serialization metadata for downstream generators.
3. Code Generation: buildStyles()#
buildStyles() in internals/client converts the ParameterNode list on an operation into a styles object literal that is embedded in each generated request function:
- Path and query parameters emit
style(when present) plusexplode. - Header and cookie parameters omit
style(those locations have fixed styles —simpleandformrespectively) and only emitexplode. - Parameters carrying neither
stylenorexplodeare omitted entirely, so operations without serialization metadata produce nostylesargument and the runtime falls back to its defaults.
Example generated output for GET /pet/findByStatus (where status has explode: true):
return request({ method: 'GET', url: '/pet/findByStatus', styles: { query: { status: { explode: true } } }, ...config })
4. Runtime Serializers#
The serializers are emitted as a shared module (.kubb/serializers.ts) alongside the generated clients.
Query: defaultQuerySerializer routes each parameter through serializeStyledQueryParam when its entry is found in the styles.query map, or through serializeDefaultQueryParam (arrays repeat as separate keys, objects use deepObject) otherwise.
Style behaviors for arrays :
| Style | explode: true | explode: false |
|---|---|---|
form (default) | id=3&id=4 | id=3,4 |
spaceDelimited | id=3&id=4 | id=3%204%205 |
pipeDelimited | id=3&id=4 | id=3|4|5 |
deepObject | — | a[b]=1 |
Path: defaultPathSerializer defaults to simple style with explode: false and handles label (.-prefix) and matrix (;name=-prefix) styles for arrays and objects.
Header: applyHeaderStyles applies simple style using the explode flag only — header values are not URL-encoded.
Cookie: serializeCookies uses form style; without explode, arrays join with commas; with explode, each value gets its own name=value pair joined by ; .
5. additionalProperties and explode: true Flattening#
A separate code-gen concern: when a query parameter has style: form, explode: true, and its schema is an object with only additionalProperties (no explicit properties), the generated TypeScript type should be a flat index signature rather than a nested property.
PR #2195 fixed this in packages/oas/src/Oas.ts (getParametersSchema): such parameters are detected and their additionalProperties is promoted to the root-level query params schema, producing { [key: string]: string } instead of { customFields?: { [key: string]: string } }.
Key Source Files#
| File | Purpose |
|---|---|
packages/ast/src/nodes/parameter.ts | ParameterNode type with style / explode |
packages/adapter-oas/src/parser.ts | Reads style/explode from OAS spec |
internals/client/src/builders/styles.ts | Emits per-operation styles literal |
packages/plugin-fetch/templates/serializers.ts | Runtime serializer implementations |
tests/3.0.x/__snapshots__/pluginFetch/default/clients/findPetsByStatus.ts | Generated client snapshot with styles |
tests/3.0.x/__snapshots__/pluginFetch/default/.kubb/serializers.ts | Generated serializers snapshot |