Skip to content

[csharp] Fix oneOf/anyOf response deserialization in httpclient library - #25080

Merged
wing328 merged 3 commits into
OpenAPITools:masterfrom
cguldner:fix/csharp-httpclient-oneof-fromjson
Oct 5, 2026
Merged

wing328 merged 3 commits into
OpenAPITools:masterfrom
cguldner:fix/csharp-httpclient-oneof-fromjson

Conversation

@cguldner

@cguldner cguldner commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #25079

Problem

In the csharp httpclient library, ApiClient.ExecAsync has a special case for oneOf/anyOf response types (types derived from AbstractOpenAPISchema): it reflectively invokes the model's static FromJson(string jsonString) method.

The call passed response.Content — an HttpContent — rather than the JSON string that FromJson expects. Because the invocation goes through reflection, it compiled fine but threw at runtime on every oneOf/anyOf response:

System.ArgumentException: Object of type 'HttpConnectionResponseContent' cannot be converted to type 'System.String'.

This affects any operation whose response schema uses oneOf/anyOf, and has been present since the httpclient library gained AbstractOpenAPISchema support.

Fix

Read the response body as a string first and pass that to FromJson. The content is already buffered by the preceding Deserialize<T> call, so re-reading it is safe:

string jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
responseData = (T)typeof(T).GetMethod("FromJson").Invoke(null, new object[] { jsonString });

Note: the restsharp library is not affected — there response.Content is already a string.

Changes

  • Updated template modules/openapi-generator/src/main/resources/csharp/libraries/httpclient/ApiClient.mustache.
  • Regenerated the affected csharp httpclient samples (net9, net10, net10 non-public, net9 non-public, standard2.0).

PR checklist

  • Read the contribution guidelines.
  • Ran generation and committed all changed files. Only the httpclient template and its generated samples changed.
  • Filed as targeting master.

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


Summary by cubic

Fixes oneOf/anyOf response deserialization in the csharp httpclient library so these responses no longer throw at runtime.

ApiClient.ExecAsync was passing response.Content (an HttpContent) into the model's reflective FromJson(string) call, which threw ArgumentException on every such response. The fix reads the response body as a string first and passes that; the content is already buffered by the preceding Deserialize<T> call, so re-reading is safe. The restsharp library is unaffected because its response.Content is already a string.

  • Adds a regression test to the net9, net10, non-public, and standard2.0 httpclient samples that exercises GetAsync<Pig> (a oneOf) through a stub handler, asserts the correct concrete type is deserialized, and disposes the injected HttpClient and ApiClient.
  • Updates the httpclient template and its regenerated samples.

Fixes #25079.

Written for commit 9da6b3b. Summary will update on new commits.

Review in cubic

In the httpclient library's ApiClient.ExecAsync, responses whose schema
is oneOf/anyOf (types derived from AbstractOpenAPISchema) are deserialized
by reflectively invoking the model's static FromJson(string) method.

The call passed response.Content (an HttpContent) instead of the JSON
string FromJson expects. Because the invocation is reflective it compiled
but threw ArgumentException at runtime ("object of type
HttpConnectionResponseContent cannot be converted to type String") on
every oneOf/anyOf response.

Read the response body as a string first and pass that to FromJson. The
content is already buffered by the preceding Deserialize call, so
re-reading it is safe. Regenerated the affected csharp httpclient samples.

Fixes OpenAPITools#25079

@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.

No issues found across 6 files

Re-trigger cubic

…rialization

Adds a unit test to the net10, net9 and standard2.0 httpclient Petstore
samples that drives ApiClient.GetAsync<Pig> (Pig is a oneOf) through a
stub HttpMessageHandler and asserts the response is deserialized to the
correct concrete type. Without the fix this throws the ArgumentException
reported in OpenAPITools#25079.

Refs OpenAPITools#25079
@cguldner

cguldner commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Added a regression test (OneOfResponseDeserializationTests) to the net10, net9, and standard2.0 httpclient Petstore samples. It drives ApiClient.GetAsync<Pig> (where Pig is a oneOf) through a stub HttpMessageHandler returning {"className":"BasquePig"} and asserts the response deserializes to the correct concrete type.

Verified with .NET SDK 10.0.401:

  • With the fix: test passes.
  • Reverting the fix (passing response.Content again): the test fails with the exact error from the issue — System.ArgumentException : Object of type 'System.Net.Http.StringContent' cannot be converted to type 'System.String'.

net9 and standard2.0 test projects build cleanly as well.

@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.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools.Test/Client/OneOfResponseDeserializationTests.cs">

<violation number="1" location="samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools.Test/Client/OneOfResponseDeserializationTests.cs:26">
P2: This regression test is hand-written into a generated sample directory with no mustache template backing it, so it is not reproducible from the generator. A clean sample regeneration (bin/generate-samples.sh rebuilds these outputs, and several csharp sample scripts remove the target directory first) will silently drop the coverage this PR adds; the net9/net10 non-public variants, which got the same ApiClient fix, also don't receive the test. Back the test with a C# template (e.g., a small mustache emitted into the Test source set) and regenerate all affected variants, or at minimum confirm the regen scripts preserve this file.</violation>
</file>

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

Re-trigger cubic

ApiClient does not own an injected HttpClient, so the test must dispose it
itself. Use 'using' declarations for both the HttpClient and ApiClient.

Addresses review feedback on OpenAPITools#25080.
@wing328 wing328 added this to the 7.26.0 milestone Oct 5, 2026
@wing328
wing328 merged commit d33cbf8 into OpenAPITools:master Oct 5, 2026
51 checks passed
@wing328

wing328 commented Oct 5, 2026

Copy link
Copy Markdown
Member

thanks for the fix, which has been merged.

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] httpclient: oneOf/anyOf responses throw ArgumentException because FromJson is invoked with HttpContent

2 participants