Fix C# GenericHost oneOf serialization for referenced models - #24674
Fix C# GenericHost oneOf serialization for referenced models#24674madhus1218 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
3 issues found across 3 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/csharp/libraries/generichost/JsonConverter.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache:386">
P2: For primitive oneOf members the new code serializes the value as a bare JSON primitive and `return`s before `writer.WriteStartObject()` is reached. This path is asymmetric with the referenced-object path it sits next to: the object path preserves the parent's sibling properties via WriteProperties, but the primitive path writes only the bare scalar and drops every sibling property (and never emits an object). For a composed model that mixes a primitive oneOf member with sibling properties (e.g. `oneOf: [string, Apple]` plus a `color` sibling), serializing when the primitive is selected silently omits the sibling data, so the output won't round-trip. Consider writing the primitive into the object (or documenting that a primitive oneOf cannot be combined with siblings).</violation>
<violation number="2" location="modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache:403">
P1: `oneOf` alternatives that are arrays/maps now generate a `<baseType>JsonConverter` lookup and `WriteProperties` call, so their clients fail to compile or serialize; limit this path to model references and serialize collection alternatives directly before starting an object.</violation>
<violation number="3" location="modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache:404">
P2: The referenced oneOf model's properties are written directly into the parent's already-open JSON object (flattened), followed by the parent's own WriteProperties. If a oneOf child schema shares a JSON property name with a sibling property on the parent (or with the discriminator/additionalProperties), the serialized output will contain duplicate keys for that name. The included Fruit sample avoids this only by luck (child keys kind/count/sweet vs parent key color); a child such as `apple` defining a `color` property alongside Fruit's `color` sibling would produce duplicate "color" entries, and on deserialization the parent's value silently wins because it is written last. Consider guarding against name collisions (e.g. wrapper/discriminator object or a name-overlap check) or at least documenting this flattening limitation.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| {{^isPrimitiveType}} | ||
| if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) | ||
| { | ||
| {{baseType}}JsonConverter {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}} = ({{baseType}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.GetType())); |
There was a problem hiding this comment.
P1: oneOf alternatives that are arrays/maps now generate a <baseType>JsonConverter lookup and WriteProperties call, so their clients fail to compile or serialize; limit this path to model references and serialize collection alternatives directly before starting an object.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache, line 403:
<comment>`oneOf` alternatives that are arrays/maps now generate a `<baseType>JsonConverter` lookup and `WriteProperties` call, so their clients fail to compile or serialize; limit this path to model references and serialize collection alternatives directly before starting an object.</comment>
<file context>
@@ -376,7 +376,39 @@
+ {{^isPrimitiveType}}
+ if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null)
+ {
+ {{baseType}}JsonConverter {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}} = ({{baseType}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.GetType()));
+ {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}}.WriteProperties(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions);
+ }
</file context>
| if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) | ||
| { | ||
| {{baseType}}JsonConverter {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}} = ({{baseType}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.GetType())); | ||
| {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}}.WriteProperties(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); |
There was a problem hiding this comment.
P2: The referenced oneOf model's properties are written directly into the parent's already-open JSON object (flattened), followed by the parent's own WriteProperties. If a oneOf child schema shares a JSON property name with a sibling property on the parent (or with the discriminator/additionalProperties), the serialized output will contain duplicate keys for that name. The included Fruit sample avoids this only by luck (child keys kind/count/sweet vs parent key color); a child such as apple defining a color property alongside Fruit's color sibling would produce duplicate "color" entries, and on deserialization the parent's value silently wins because it is written last. Consider guarding against name collisions (e.g. wrapper/discriminator object or a name-overlap check) or at least documenting this flattening limitation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache, line 404:
<comment>The referenced oneOf model's properties are written directly into the parent's already-open JSON object (flattened), followed by the parent's own WriteProperties. If a oneOf child schema shares a JSON property name with a sibling property on the parent (or with the discriminator/additionalProperties), the serialized output will contain duplicate keys for that name. The included Fruit sample avoids this only by luck (child keys kind/count/sweet vs parent key color); a child such as `apple` defining a `color` property alongside Fruit's `color` sibling would produce duplicate "color" entries, and on deserialization the parent's value silently wins because it is written last. Consider guarding against name collisions (e.g. wrapper/discriminator object or a name-overlap check) or at least documenting this flattening limitation.</comment>
<file context>
@@ -376,7 +376,39 @@
+ if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null)
+ {
+ {{baseType}}JsonConverter {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}} = ({{baseType}}JsonConverter) jsonSerializerOptions.Converters.First(c => c.CanConvert({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}.GetType()));
+ {{#lambda.camelcase_sanitize_param}}{{baseType}}JsonConverter{{/lambda.camelcase_sanitize_param}}.WriteProperties(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions);
+ }
+
</file context>
| {{#isPrimitiveType}} | ||
| if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) | ||
| { | ||
| JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); |
There was a problem hiding this comment.
P2: For primitive oneOf members the new code serializes the value as a bare JSON primitive and returns before writer.WriteStartObject() is reached. This path is asymmetric with the referenced-object path it sits next to: the object path preserves the parent's sibling properties via WriteProperties, but the primitive path writes only the bare scalar and drops every sibling property (and never emits an object). For a composed model that mixes a primitive oneOf member with sibling properties (e.g. oneOf: [string, Apple] plus a color sibling), serializing when the primitive is selected silently omits the sibling data, so the output won't round-trip. Consider writing the primitive into the object (or documenting that a primitive oneOf cannot be combined with siblings).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache, line 386:
<comment>For primitive oneOf members the new code serializes the value as a bare JSON primitive and `return`s before `writer.WriteStartObject()` is reached. This path is asymmetric with the referenced-object path it sits next to: the object path preserves the parent's sibling properties via WriteProperties, but the primitive path writes only the bare scalar and drops every sibling property (and never emits an object). For a composed model that mixes a primitive oneOf member with sibling properties (e.g. `oneOf: [string, Apple]` plus a `color` sibling), serializing when the primitive is selected silently omits the sibling data, so the output won't round-trip. Consider writing the primitive into the object (or documenting that a primitive oneOf cannot be combined with siblings).</comment>
<file context>
@@ -376,7 +376,39 @@
+ {{#isPrimitiveType}}
+ if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null)
+ {
+ JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions);
+ return;
+ }
</file context>
|
thanks for the PR cc @devhl-labs |
|
To better showcase that the introduced functionality handles the scenario in the issue I would suggest that the specification is modified/extended to have an inline components:
schemas:
fruit:
example:
color: color
oneOf:
- $ref: "#/components/schemas/apple"
- $ref: "#/components/schemas/banana"
- $ref: "#/components/schemas/orange"while the example schema provided has it as an inline I would also argue that it would be beneficial to have a test case for the The CSharp generator is also currently documented as not supporting |
What does this PR do?
Fixes C# GenericHost serialization for
oneOfschemas that contain referenced model types.Previously, when a
oneOfvalue was a referenced object, its properties were not written into the serialized JSON output. This update adds handling inJsonConverter.mustacheso referencedoneOfmodels delegate to their generatedWriteProperties(...)method while preserving sibling properties on the parent model.Testing
Added regression coverage for serializing a referenced
oneOfmodel.Added coverage for a referenced
oneOfmodel with a sibling property.Regenerated the C# GenericHost
OneOfsample.Verified the generated .NET 8 sample tests:
Verified
CSharpClientCodegenTest:Maven reactor build completed successfully.
Fixes #24398
PR checklist
Summary by cubic
Fixes C# GenericHost JSON serialization for
oneOfwhen the selected type is a referenced model or a primitive. ReferencedoneOfobjects now write their properties, primitives serialize as raw values, and sibling properties are preserved.oneOfmodels to theirWriteProperties(...)method inJsonConverter.mustache.oneOfvalues directly without an object wrapper.oneOfvalue.OneOf.NET 8 sample.Written for commit e55db64. Summary will update on new commits.