Skip to content

Required array query parameters lose the explode modifier (comma-joined instead of repeated keys) — explicit style/explode in the OpenAPI doc is ignored #8031

Description

@phaza

What are you generating using Kiota, clients or plugins?

API Client/SDK

In what context or format are you using Kiota?

Nuget tool (installed as a dotnet tool)

Client library/SDK language

Csharp

Describe the bug

For a query parameter of type: array, Kiota generates a URI template that comma-joins the array values (?ids=1,2,3) instead of repeating the key (?ids=1&ids=2&ids=3) whenever the parameter is marked required: true in the OpenAPI document — regardless of what style/explode is set on that parameter.

The exact same array parameter, only marked required: false, is generated correctly with the explode modifier and serializes as repeated keys at runtime.

Concretely, across three otherwise-identical operations differing only in required/style/explode on the array parameter, Kiota generates:

parameter definition generated request builder base template
required: true, no style/explode set {+baseurl}/required-array?ids={ids}
required: true, style: form, explode: true set explicitly {+baseurl}/required-array-explicit-explode?ids={ids} (identical to the row above — explode is ignored)
required: false, no style/explode set {+baseurl}/optional-array{?ids*} (correct)

And the resulting runtime request URIs (setting Ids = [1, 2, 3] on each generated query-parameters class) are:

required, no explicit style/explode                : https://example.org/required-array?ids=1,2,3
required, explicit style:form/explode:true          : https://example.org/required-array-explicit-explode?ids=1,2,3
optional, no explicit style/explode                 : https://example.org/optional-array?ids=1&ids=2&ids=3

This breaks any API where an array query parameter is required and the server expects the standard OpenAPI 3 default query serialization (style: form, explode: true, i.e. repeated keys) — which is the default per the OpenAPI 3.0 spec for in: query parameters, and is what ASP.NET Core's default model binding (and presumably most other frameworks) expects for T[]/List<T>-bound query parameters. Kiota-generated clients silently send a malformed query string and the server rejects the request (in our case, an ASP.NET Core API returned a 400 validation error), with no warning at generation time that the required: true + array combination degrades the parameter's serialization style.

We hit this on a real endpoint (GET /api/v1/{companyId}/restaurants/planner/monthlysummary?Month=...&restaurantIds=...) where restaurantIds is a required array of integer. The sibling endpoint GET /api/v1/{companyId}/pointofsale/tables has an optional array parameter (RestaurantIds) of the same shape and generates correctly ({?OnlyOpenTables*,RestaurantIds*}).

Expected behavior

An array-typed query parameter should generate a URI template with the explode modifier ({?ids*} grouping, or equivalent) whenever style: form/explode: true applies (the OpenAPI 3.0 default, or explicitly declared) — independent of whether the parameter is required. required should only affect whether the SDK enforces the value being supplied, not how the value is serialized into the query string.

How to reproduce

  1. Save the OpenAPI description below as repro.yaml.
  2. Run:
    kiota generate --language csharp --openapi repro.yaml --output kiota-out --class-name ReproClient --namespace-name ReproSDK --clean-output
    
  3. Inspect the generated *RequestBuilder.cs constructors for each of the three operations — see the base(requestAdapter, "...", ...) call's URL template string.
  4. Optionally, build a tiny console app referencing the generated code + Microsoft.Kiota.Http.HttpClientLibrary (1.22.2) and print ToGetRequestInformation(cfg => cfg.QueryParameters.Ids = new int?[] { 1, 2, 3 }).URI for each operation to see the actual malformed vs. correct runtime URIs shown above.
openapi: 3.0.1
info:
  title: repro
  version: "1.0"
servers:
  - url: https://example.org
paths:
  /required-array:
    get:
      operationId: getRequiredArray
      parameters:
        - name: ids
          in: query
          required: true
          schema:
            type: array
            items:
              type: integer
      responses:
        "200":
          description: ok
  /required-array-explicit-explode:
    get:
      operationId: getRequiredArrayExplicitExplode
      parameters:
        - name: ids
          in: query
          required: true
          style: form
          explode: true
          schema:
            type: array
            items:
              type: integer
      responses:
        "200":
          description: ok
  /optional-array:
    get:
      operationId: getOptionalArray
      parameters:
        - name: ids
          in: query
          required: false
          schema:
            type: array
            items:
              type: integer
      responses:
        "200":
          description: ok

Open API description file

Inline above (also attaching repro.yaml).

Kiota Version

1.34.1+9f9cfb3b1cb9b5311a214ea6ce0f69943c523005

Latest Kiota version known to work for scenario above?(Not required)

Not tested against earlier versions — the fact that required and optional array parameters go through visibly different template-construction branches suggests this may not be a recent regression, but we haven't bisected it.

Known Workarounds

  1. Server-side (OpenAPI doc): declare the array parameter as required: false even if it's functionally required — enforce "must be provided" via the API's own validation layer instead of the OpenAPI required flag. This is enough to make Kiota emit the correct exploded template. (Downside: the generated client's documentation/nullability annotations no longer reflect that the parameter is actually mandatory.)
  2. Client-side: bypass the generated request builder's query-parameter binding entirely by constructing the query string manually and calling the generated WithUrl(rawUrl) method on the request builder, which accepts an arbitrary pre-built URL and skips the broken template.

Configuration

  • OS: macOS 26.3.1 (Darwin 25.3.0)
  • architecture: arm64 (Apple Silicon)
  • Kiota installed via dotnet tool install --global Microsoft.OpenApi.Kiota
  • .NET SDK: 10.0.302
  • Not verified whether other target languages (Go, Java, TypeScript, Python, etc.) exhibit the same behavior — we only tested --language csharp.

Other information

The divergence appears to be in how Kiota's URI-template builder decides where to place a query parameter: parameters that are optional get grouped into a reserved-expansion block ({?a*,b*}) that preserves the explode modifier per-parameter, while parameters marked required: true get inlined directly into the base template as plain name={name} simple-string expansion — which has no concept of "explode" for arrays and falls back to comma-joining. This branch looks like it does not consult the parameter's style/explode schema properties at all once a parameter is required, which is why setting them explicitly makes no difference (verified above).

Possibly related (reports comma-joined arrays, but doesn't identify required as the trigger): #6488.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✔️

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions