From 2b7b258f7f7d600e61c2ee1c61bb6a501b3af66a Mon Sep 17 00:00:00 2001 From: Simon Schulte Date: Fri, 27 Mar 2026 11:17:42 +0100 Subject: [PATCH 1/2] fix(csharp): use enum rendering functions for array-of-enum properties in JSON converter Array-typed properties whose items are enums fell through to the generic JsonSerializer.Serialize/Deserialize path, which ignored the defined *ValueConverter.ToJsonValue/*FromStringOrDefault functions and serialized enum values using the default .NET JSON behaviour (PascalCase names). Add explicit {{#items.isEnum}} branches in both the read (Deserialize) and write (Serialize) paths of the generated JsonConverter so that each item in an enum array is processed via the same converter functions used for scalar enum properties. Co-Authored-By: Claude Sonnet 4.6 --- .../generichost/JsonConverter.mustache | 46 +++++++++++++++++ .../test/resources/3_0/csharp/enum-array.yaml | 51 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 modules/openapi-generator/src/test/resources/3_0/csharp/enum-array.yaml diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache index 24810c9cee43..6c58c1f67b54 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache @@ -251,7 +251,21 @@ {{^isNumeric}} {{^isDate}} {{^isDateTime}} + {{#items.isEnum}} + var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items = new List<{{#items.isInnerEnum}}{{classname}}.{{/items.isInnerEnum}}{{{items.datatypeWithEnum}}}>(); + while (utf8JsonReader.Read()) + { + if (utf8JsonReader.TokenType == JsonTokenType.EndArray) + break; + string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue = utf8JsonReader.GetString(); + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue != null) + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items.Add({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}); + } + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items); + {{/items.isEnum}} + {{^items.isEnum}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}JsonSerializer.Deserialize<{{{datatypeWithEnum}}}>(ref utf8JsonReader, jsonSerializerOptions){{^isNullable}}{{nrt!}}{{/isNullable}}); + {{/items.isEnum}} {{/isDateTime}} {{/isDate}} {{/isNumeric}} @@ -612,14 +626,30 @@ if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} != null) { writer.WritePropertyName("{{baseName}}"); + {{^items.isEnum}} JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/items.isEnum}} + {{#items.isEnum}} + writer.WriteStartArray(); + foreach (var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item in {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}) + {{^items.isNumeric}}writer.WriteStringValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}}{{#items.isNumeric}}writer.WriteNumberValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}} + writer.WriteEndArray(); + {{/items.isEnum}} } else writer.WriteNull("{{baseName}}"); {{/isNullable}} {{^isNullable}} writer.WritePropertyName("{{baseName}}"); + {{^items.isEnum}} JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/items.isEnum}} + {{#items.isEnum}} + writer.WriteStartArray(); + foreach (var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item in {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}) + {{^items.isNumeric}}writer.WriteStringValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}}{{#items.isNumeric}}writer.WriteNumberValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}} + writer.WriteEndArray(); + {{/items.isEnum}} {{/isNullable}} {{/required}} {{^required}} @@ -628,7 +658,15 @@ if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value != null) { writer.WritePropertyName("{{baseName}}"); + {{^items.isEnum}} JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/items.isEnum}} + {{#items.isEnum}} + writer.WriteStartArray(); + foreach (var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item in {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value{{nrt!}}) + {{^items.isNumeric}}writer.WriteStringValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}}{{#items.isNumeric}}writer.WriteNumberValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}} + writer.WriteEndArray(); + {{/items.isEnum}} } else writer.WriteNull("{{baseName}}"); @@ -636,7 +674,15 @@ {{^isNullable}} { writer.WritePropertyName("{{baseName}}"); + {{^items.isEnum}} JsonSerializer.Serialize(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}, jsonSerializerOptions); + {{/items.isEnum}} + {{#items.isEnum}} + writer.WriteStartArray(); + foreach (var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item in {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{nrt!}}) + {{^items.isNumeric}}writer.WriteStringValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}}{{#items.isNumeric}}writer.WriteNumberValue({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Item){{/items.isInnerEnum}});{{/items.isNumeric}} + writer.WriteEndArray(); + {{/items.isEnum}} } {{/isNullable}} {{/required}} diff --git a/modules/openapi-generator/src/test/resources/3_0/csharp/enum-array.yaml b/modules/openapi-generator/src/test/resources/3_0/csharp/enum-array.yaml new file mode 100644 index 000000000000..26e5f6873504 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/csharp/enum-array.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + title: Enum Array Test + description: >- + Tests that array-of-enum properties are serialized and deserialized using + the defined enum rendering functions rather than falling back to default + PascalCase JSON serialization. + version: 1.0.0 + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +paths: + /shipments: + get: + operationId: GetShipments + tags: + - Shipments + responses: + '200': + description: Returns a list of shipments + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Shipment' +servers: + - url: 'http://localhost' +components: + schemas: + Shipment: + type: object + required: + - id + - allowedMethods + properties: + id: + type: integer + format: int32 + allowedMethods: + type: array + items: + $ref: '#/components/schemas/ShippingMethod' + preferredMethod: + $ref: '#/components/schemas/ShippingMethod' + ShippingMethod: + type: string + enum: + - standard + - express + - overnight From d0ba6160858aeff09afa8b57bd69c7fee6c50802 Mon Sep 17 00:00:00 2001 From: Simon Schulte Date: Fri, 27 Mar 2026 12:28:16 +0100 Subject: [PATCH 2/2] fix(csharp): guard array-of-enum deserialization against null JSON values Without the null check, a JSON null value for an array-of-enum property caused the reader to advance past the null token and attempt to read the next property as array content, desynchronizing the Utf8JsonReader. --- .../generichost/JsonConverter.mustache | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache index 6c58c1f67b54..813a58bd2316 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache @@ -252,16 +252,19 @@ {{^isDate}} {{^isDateTime}} {{#items.isEnum}} - var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items = new List<{{#items.isInnerEnum}}{{classname}}.{{/items.isInnerEnum}}{{{items.datatypeWithEnum}}}>(); - while (utf8JsonReader.Read()) + if (utf8JsonReader.TokenType != JsonTokenType.Null) { - if (utf8JsonReader.TokenType == JsonTokenType.EndArray) - break; - string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue = utf8JsonReader.GetString(); - if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue != null) - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items.Add({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}); + var {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items = new List<{{#items.isInnerEnum}}{{classname}}.{{/items.isInnerEnum}}{{{items.datatypeWithEnum}}}>(); + while (utf8JsonReader.Read()) + { + if (utf8JsonReader.TokenType == JsonTokenType.EndArray) + break; + string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue = utf8JsonReader.GetString(); + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue != null) + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items.Add({{^items.isInnerEnum}}{{{items.datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}{{#items.isInnerEnum}}{{classname}}.{{{items.datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ItemRawValue){{/items.isInnerEnum}}); + } + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items); } - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}Items); {{/items.isEnum}} {{^items.isEnum}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}JsonSerializer.Deserialize<{{{datatypeWithEnum}}}>(ref utf8JsonReader, jsonSerializerOptions){{^isNullable}}{{nrt!}}{{/isNullable}});