Skip to content

[kotlin][jvm-ktor] Fix query parameters typed as type:object (Map and model)#23763

Merged
wing328 merged 1 commit into
OpenAPITools:masterfrom
AnarchyGhost:fix/jvm-ktor-type-object-query
May 12, 2026
Merged

[kotlin][jvm-ktor] Fix query parameters typed as type:object (Map and model)#23763
wing328 merged 1 commit into
OpenAPITools:masterfrom
AnarchyGhost:fix/jvm-ktor-type-object-query

Conversation

@AnarchyGhost
Copy link
Copy Markdown
Contributor

@AnarchyGhost AnarchyGhost commented May 11, 2026

Fixes #23762

Summary

Reworks query parameter handling in the jvm-ktor template to correctly serialize type: object schemas, plus a small KotlinClientCodegen change so the template can build correct deepObject URLs for models.

Three branches in kotlin-client/libraries/jvm-ktor/api.mustache:

  • isModel
    • deepObject → per-field, key = paramBaseName[fieldBaseName]
    • form+explode=true → per-field, key = fieldBaseName
    • form+explode=false → single entry, CSV of field,value pairs (non-null fields only) under baseName
  • isMap
    • deepObject → forEach with baseName[$key]
    • form+explode=true → forEach with bare $key
    • form+explode=false → joinToString(",") of map entries under baseName
  • default (scalar / array) — unchanged

Behavior after the fix

schema style / explode URL
Map (additionalProperties:) form / true ?k1=v1&k2=v2
Map form / false ?param=k1,v1,k2,v2
Map deepObject ?param[k1]=v1&param[k2]=v2
Model (properties:) form / true ?field1=…&field2=…
Model form / false ?param=field1,value1,field2,value2
Model deepObject ?paramBaseName[field1]=…&paramBaseName[field2]=…

Implementation notes

KotlinClientCodegen now exposes the outer parameter's OAS baseName on each generated field via the x-kotlin-param-base-name vendor extension. The jvm-ktor template uses it for the deepObject URL prefix on models, since Mustache provides no access to the outer scope from inside {{#vars}}.

Note on parameter naming (snake_case vs camelCase)

For models with style: deepObject, the URL prefix is the OAS baseName, not the Kotlin identifier. Example: a parameter declared as

- name: model_deep
  in: query
  style: deepObject
  schema:
    type: object
    properties:
      a:
        type: string

generates a Kotlin parameter named modelDeep (camelCase), but the URL key remains ?model_deep[a]=… per OAS. This is the role of the new x-kotlin-param-base-name vendor extension: it propagates the parameter's baseName into the vars scope, which Mustache otherwise cannot reach.

For comparison, the existing jvm-okhttp template currently emits ?modelDeep[a]=… (kotlin paramName) in the same scenario. This PR does not touch jvm-okhttp — scope is intentionally limited to jvm-ktor.

Test plan

  • New unit test KotlinClientCodegenApiTest#testJvmKtorQueryParamWithTypeObject covering the 6 affected (schema × style/explode) combinations plus a regression assertion that the old broken mapDeep?.apply { shape is gone.
  • All 71 KotlinClient*Test cases pass.
  • ./bin/generate-samples.sh ./bin/configs/kotlin-jvm-ktor-{gson,jackson,kotlinx_serialization}.yamlno existing sample changed, the fix only activates new branches for previously broken cases.
  • Additional runtime sanity check on a private playground: generated client routed through Ktor MockEngine produces the URLs in the table above for all 6 cases.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache:137">
P1: Model query params ignore `explode=false` and always serialize each property as its own query key.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

… model)

The query parameter block in jvm-ktor/api.mustache used a single
toMultiValue(...) call for every container type. Map<K,V> is not Iterable,
so any query parameter with schema `type: object, additionalProperties: ...`
failed to compile. Likewise, query parameters with `type: object, properties: ...`
compiled but emitted toString() of the generated data class into the URL
instead of OAS-defined serialization.

Split the queryParams block into three branches:

  - isModel  -> deepObject: per-field, key = paramBaseName[fieldBaseName]
                form+explode=true:  per-field, key = fieldBaseName
                form+explode=false: single entry, CSV of "field,value" pairs
                                    (non-null fields only) under baseName
  - isMap    -> deepObject: forEach with baseName[$key]
                form+explode=true:  forEach with bare $key
                form+explode=false: joinToString(",") under baseName
  - default  -> unchanged (scalar/array path via toMultiValue or listOf)

KotlinClientCodegen now exposes the outer parameter's OAS baseName on each
generated field via the `x-kotlin-param-base-name` vendor extension. The
jvm-ktor template uses it to build correct deepObject URL keys for models,
since Mustache provides no access to the outer scope from inside {{#vars}}.

Added KotlinClientCodegenApiTest#testJvmKtorQueryParamWithTypeObject
covering the 9 (schema x style/explode) combinations.

No existing sample changed (verified via ./bin/generate-samples.sh on the
three jvm-ktor configs): the fix only activates new branches for previously
broken cases.
@AnarchyGhost AnarchyGhost force-pushed the fix/jvm-ktor-type-object-query branch from d3c4498 to deeace1 Compare May 11, 2026 19:17
@AnarchyGhost
Copy link
Copy Markdown
Contributor Author

AnarchyGhost commented May 11, 2026

@cubic-dev-ai Good catch — the isModel branch missed form + explode=false. Fixed in deeace10b55:

  • Added a third sub-branch under {{#isModel}} for {{^isExplode}} (non-deepObject), generating a single query entry with the OAS-defined field,value CSV under baseName. Null fields are filtered via listOfNotNull, and the parameter is omitted entirely if all fields are null.
  • Extended jvm-ktor-type-object-query.yaml with a model_form_noexplode parameter and added a corresponding assertion in KotlinClientCodegenApiTest#testJvmKtorQueryParamWithTypeObject.
  • Verified the resulting URL via a Ktor MockEngine smoke test on a local playground: ProbeModel(a="hi", b=7) serializes to ?model_form_noexplode=a,hi,b,7 (URL-encoded %2C as expected).

All 71 KotlinClient*Test cases still pass; no existing samples changed.

@wing328
Copy link
Copy Markdown
Member

wing328 commented May 12, 2026

Ran some tests locally and the result is good.

Thanks for contributing the fix.

@wing328 wing328 merged commit 1fcc769 into OpenAPITools:master May 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][kotlin][jvm-ktor] Query parameters typed as type:object (Map/model) generate broken code

2 participants