Skip to content

[csharp] Serialize header parameters with content: application/json as JSON - #25083

Open
cguldner wants to merge 8 commits into
OpenAPITools:masterfrom
cguldner:fix/csharp-json-header-content
Open

cguldner wants to merge 8 commits into
OpenAPITools:masterfrom
cguldner:fix/csharp-json-header-content

Conversation

@cguldner

@cguldner cguldner commented Oct 1, 2026 •

Copy link
Copy Markdown

Fixes #25082

Problem

A header parameter declared with content: application/json was serialized in the generated C# client through ClientUtils.ParameterToString(...). For a model object, ParameterToString falls through to Convert.ToString(obj, CultureInfo.InvariantCulture), which emits the model's debug ToString():

class HeaderArg {
  Path: /x
}

instead of JSON ({"path":"/x"}). This affects all C# client libraries, since they share this header serialization path: restsharp (default), httpclient, generichost, and unityWebRequest.

This is the C# counterpart of the Java bug #25055 (PR #25056) and the Python bug #25067 (PR #25068).

Fix

Mirror the existing queryIsJsonMimeType mechanism (which already does exactly this for query params):

  • Engine: add a shared CodegenParameter.headerIsJsonMimeType flag, set in DefaultCodegen.fromParameter via the already-existing isJsonMimeType(contentType) helper. (This is the same small engine addition proposed in fix(java): serialize JSON header parameter content #25056; if that merges first, this can be de-duped.)
  • Templates: the C# api.mustache templates serialize JSON-content headers as JSON instead of via ParameterToString, keeping existing behavior for regular headers:
    • restsharp, httpclient, unityWebRequest (Newtonsoft): a new ASCII-safe ClientUtils.ParameterToJsonString(...) helper (uses StringEscapeHandling.EscapeNonAscii).
    • generichost (System.Text.Json): JsonSerializer.Serialize(value, _jsonSerializerOptions), consistent with how it already serializes request bodies.

Generated code for a JSON-content header now looks like:

// restsharp / httpclient / unityWebRequest
localVarRequestOptions.HeaderParameters.Add("X-Json-Arg", Org.OpenAPITools.Client.ClientUtils.ParameterToJsonString(xJsonArg)); // header parameter
// generichost
httpRequestMessageLocalVar.Headers.Add("X-Json-Arg", JsonSerializer.Serialize(xJsonArg, _jsonSerializerOptions));

while a regular header is unchanged (ParameterToString(xPlainArg)).

Tests

  • Added 3_1/csharp/json-header-content.yaml (a JSON-content header + a plain header).
  • Added testJsonContentHeaderUsesJsonSerialization (restsharp) and testJsonContentHeaderUsesJsonSerializationGenericHost to CSharpClientCodegenTest, asserting the JSON header is JSON-serialized and the plain header is not.

Verification

  • CSharpClientCodegenTest — both new tests pass (Tests run: 2, Failures: 0).
  • Generated a client from the fixture for all four libraries and confirmed the header serialization code; restsharp, httpclient, and generichost outputs compile with dotnet build (0 errors). unityWebRequest output generates the expected code (requires the Unity SDK to compile).
  • Regenerated samples: the only change is the new ClientUtils.ParameterToJsonString helper added to the Newtonsoft-based generated clients.

PR checklist

  • Read the contribution guidelines.
  • Ran ./mvnw clean package for the generator and regenerated the affected samples with ./bin/generate-samples.sh bin/configs/csharp*.yaml; committed all changed files.
  • Targeting master.

cc C# technical committee: @mandrean @shibayan @Blackclaws @lucamazzanti


Summary by cubic

Fixes the C# generator so header parameters declared with content: application/json serialize as JSON instead of the model's debug ToString() output, which affected all C# client libraries.

Adds a headerIsJsonMimeType flag to CodegenParameter, set in DefaultCodegen.fromParameter for both JSON and vendor +json media types, mirroring the existing query-parameter mechanism.

  • The restsharp, httpclient, and unityWebRequest templates use a new ClientUtils.ParameterToJsonString helper for JSON-content headers; generichost uses JsonSerializer.Serialize(value, _jsonSerializerOptions). Regular headers keep using ParameterToString.
  • ParameterToJsonString serializes directly with JsonConvert.SerializeObject and escapes every character below U+0020 or at/above U+007F as \uXXXX to keep header values free of control characters.
  • Adds 3.1 fixtures and regression tests for the restsharp and generichost backends covering JSON, vendor +json, plain, and constant headers.

Written for commit 834a599. Summary will update on new commits.

Review in cubic

A header parameter declared with `content: application/json` was
serialized through `ClientUtils.ParameterToString(...)`, which for a
model object falls through to `Convert.ToString(obj, ...)` and emits the
model's debug `ToString()` representation instead of JSON. This affects
every C# client library.

Mirror the existing `queryIsJsonMimeType` mechanism: add a shared
`headerIsJsonMimeType` flag to CodegenParameter, set in
DefaultCodegen.fromParameter via the existing isJsonMimeType(contentType)
helper. The C# api templates use it to serialize JSON-content headers as
JSON instead of via ParameterToString, keeping existing behavior for
regular headers:

- restsharp, httpclient, unityWebRequest (Newtonsoft): a new
  ClientUtils.ParameterToJsonString helper producing ASCII-safe JSON.
- generichost (System.Text.Json): JsonSerializer.Serialize(value,
  _jsonSerializerOptions), consistent with how it serializes bodies.

Adds a 3.1 fixture and regression tests for the restsharp and generichost
serialization backends.

Fixes OpenAPITools#25082
Adds the ClientUtils.ParameterToJsonString helper to the generated
Newtonsoft-based clients (restsharp, httpclient, unityWebRequest).

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 32 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread modules/openapi-generator/src/main/resources/csharp/ClientUtils.mustache Outdated
Comment thread modules/openapi-generator/src/main/resources/csharp/api.mustache
- ParameterToJsonString: escape every character >= U+007F (including DEL)
  as \uXXXX by post-processing the final JSON string, mirroring the Java
  fix. This is more robust than Newtonsoft's EscapeNonAscii, which leaves
  DEL (U+007F) unescaped and is bypassed by converters that emit raw JSON.
- DefaultCodegen: also treat vendor JSON media types (application/vnd.*+json)
  as JSON headers via isJsonVendorMimeType, so e.g. application/vnd.acme+json
  is serialized as JSON instead of ToString().
- Apply the JSON-aware serializer to constant (autoset) header parameters
  in the restsharp, httpclient and generichost templates.
- Extend the fixture with a vendor +json header and add regression tests
  for the vendor and constant-header cases.

Addresses review feedback on OpenAPITools#25083.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 31 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

- ParameterToJsonString: serialize with JsonConvert.SerializeObject directly
  so a null value becomes the JSON literal "null" again (reusing Serialize
  regressed this to a C# null), and escape every character < U+0020 (C0
  controls incl. CR/LF) in addition to >= U+007F. Escaping raw control
  characters prevents header splitting/injection if a converter emits raw
  JSON control bytes.
- Constant (autoset) JSON headers now render the constant as its schema type:
  a string constant serializes to a JSON string, an integer/boolean constant
  to a JSON number/boolean (previously always a quoted string).
- Extend the constant fixture/test to cover both a string and an integer
  constant JSON header.

Addresses review feedback on OpenAPITools#25083.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 29 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread modules/openapi-generator/src/main/resources/csharp/api.mustache Outdated
Comment thread modules/openapi-generator/src/main/resources/csharp/api.mustache Outdated
- Revert the JSON-aware serialization of autoset constant header
  parameters. An autoset (single fixed enum value) header that also
  declares content: application/json is not a real-world scenario, and
  correctly serializing arbitrary scalar constant types as JSON source
  literals (string vs number vs boolean, plus C# literal escaping) adds
  disproportionate template complexity. Constants keep the existing
  ParameterToString behavior. The reported bug (variable JSON headers)
  remains fixed.
- Update the ParameterToJsonString doc comment to note that C0 control
  characters (including CR/LF) are escaped too.

Addresses review feedback on OpenAPITools#25083.

This branch has not been deployed

No deployments
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][csharp] Header parameter with content: application/json is serialized via ParameterToString() instead of JSON

2 participants