Skip to content

[BUG][csharp] Fix integer/numeric enum JSON converters using string operations instead of numeric#1

Open
Copilot wants to merge 9 commits intomasterfrom
copilot/fix-enum-string-generation
Open

[BUG][csharp] Fix integer/numeric enum JSON converters using string operations instead of numeric#1
Copilot wants to merge 9 commits intomasterfrom
copilot/fix-enum-string-generation

Conversation

Copy link

Copilot AI commented Feb 6, 2026

  • Analyze the issue: C# generichost enum JSON converters always use string operations even for integer enums
  • Fix modelEnum.mustache template to handle integer enums properly
  • Add EnumJsonReaderMethod.mustache partial to resolve correct Utf8JsonReader method
  • Fix double/float/decimal enum handling with switch-case ToJsonValue
  • Use consistent validated Read pattern for ALL numeric types
  • Remove InvariantCulture from ToString calls
  • Fix nullable string enum Write to use WriteNullValue() instead of WriteStringValue("null")
  • Fix nullable enum Read to check JsonTokenType.Null for ALL types
  • Emit : long underlying type for int64 enums to prevent overflow
  • Use C# standard formatting with space before colon in enum declarations (: long, : byte)
  • Add test assertions for enum underlying types
  • Regenerate all affected sample files
  • All 64 existing C# tests pass with no regressions
Original prompt

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

Enums are always generated to be strings. Even when the type is integer. This generates code like

public static MyEnum? FromStringOrDefault(string value)
        {
            if (value.Equals((0).ToString()))
                return MyEnum.None;

            if (value.Equals((1).ToString()))
                return MyEnum.Some;

            return null;
        }

which is on one hand silly, on the other this actually causes fatal issues.

openapi-generator version

docker:openapitools/openapi-generator-cli:v7.14.0@sha256:a620610d9fabf7ce05310c648417ba168125aac2f4517580030e115921ac1a52

OpenAPI declaration file content or url
Openapi spec
{
  "openapi": "3.0.1",
  "info": {
    "title": "Example",
    "version": "v1"
  },
  "paths": {
    "/some/api": {
      "get": {
        "tags": [
          "Example"
        ],
        "description": "",
        "operationId": "ExampleApi",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json;charset=utf-8": {
                "schema": {
                  "$ref": "#/components/schemas/MyEnum"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "MyEnum": {
        "enum": [
          0,
          1
        ],
        "type": "integer",
        "description": "description",
        "format": "int32",
        "x-enum-varnames": [
          "None",
          "Some"
        ],
        "x-enum-descriptions": [
          "This is none",
          "this is some"
        ]
      }
    }
  },
  "tags": [
    {
      "name": "Example"
    }
  ]
}
Generation Details
 docker run --rm -v "${PWD}":/local openapitools/openapi-generator-cli:v7.14.0@sha256:a620610d9fabf7ce05310c648417ba168125aac2f4517580030e115921ac1a52 generate -i /local/openapi.json -g csharp -o /local/artifacts/out
Steps to reproduce

create the above json and run the above command

Related issues/PRs

OpenAPITools#21204
OpenAPITools#15204
OpenAPITools#8458

at this point I stopped looking

Actual vs expected

The spec describes an integer enum, the code handles it like a string. Since the default c# mode is generichost which uses System.Text.Json, it uses Utf8JsonReader and for very low level optimisation reason the reader differentiates string values from number values.

The problematic code is in MyEnumJsonConverter which does:

public override MyEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    string? rawValue = reader.GetString(); // <--- this line throws exception

    MyEnum? result = rawValue == null
        ? null
        : MyEnumValueConverter.FromStringOrDefault(rawValue);

    if (result != null)
        return result.Value;

    throw new JsonException();
}

the exception:

System.InvalidOperationException: Cannot get the value of a token type 'Number' as a string.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType)
   at System.Text.Json.Utf8JsonReader.GetString()

The same principle applies to the Write method. It gets written as a string, which violates the spec!

Suggest a fix

The generator should respect the "type": "integer" in the openapi spec and handle the underlying type as such.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix enum generation to output integer types [BUG][csharp] Fix integer enum JSON converters using string operations instead of numeric Feb 6, 2026
Copilot AI requested a review from alexaka1 February 6, 2026 15:25
@alexaka1 alexaka1 requested a review from Copilot February 6, 2026 15:38
@alexaka1
Copy link
Owner

alexaka1 commented Feb 6, 2026

@copilot make an empty commit and push it to re trigger CI

Copy link
Author

Copilot AI commented Feb 6, 2026

@copilot make an empty commit and push it to re trigger CI

Pushed commit f18db57e to re-trigger CI.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a critical bug in the C# generichost code generator where integer enum JSON converters incorrectly used string operations (reader.GetString() and writer.WriteStringValue()), causing System.InvalidOperationException when deserializing integer enums from JSON and violating the OpenAPI spec by serializing integers as strings.

Changes:

  • Modified modelEnum.mustache to conditionally generate numeric or string JSON operations based on enum type
  • Added new EnumJsonReaderMethod.mustache partial template that selects the appropriate Utf8JsonReader method (GetInt32, GetInt64, GetSingle, GetDouble, GetDecimal) based on the numeric type
  • Added test spec integer-enum.yaml and corresponding test case to verify numeric operations are generated for integer enums
  • Regenerated 76 sample files across all generichost variants (net4.7–net10, standard2.0, NRT, SourceGeneration, FormModels)

Reviewed changes

Copilot reviewed 78 out of 78 changed files in this pull request and generated no comments.

Show a summary per file
File Description
modelEnum.mustache Core template fix: gates Read/Write methods on isString to use numeric operations for integer enums
EnumJsonReaderMethod.mustache New partial template resolving correct Utf8JsonReader method per numeric type
integer-enum.yaml Test specification with int32/int64 enums for validation
CSharpClientCodegenTest.java Test asserting generated code uses numeric operations, not string operations
Sample files (76 files) Regenerated enum model files showing correct numeric JSON converter implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@alexaka1 alexaka1 force-pushed the copilot/fix-enum-string-generation branch 2 times, most recently from ca04f06 to e6700bf Compare February 7, 2026 09:30
@alexaka1 alexaka1 marked this pull request as ready for review February 7, 2026 09:36
…in C# generichost

Integer enums now correctly use reader.GetInt32()/GetInt64() and
writer.WriteNumberValue() instead of reader.GetString() and
writer.WriteStringValue() in the generated JsonConverter classes.

This fixes System.InvalidOperationException when deserializing integer
enum values from JSON numbers.

Co-authored-by: copilot-swe-agent[bot] <198982749+copilot@users.noreply.github.com>
@alexaka1 alexaka1 force-pushed the copilot/fix-enum-string-generation branch from e6700bf to 29ce4b6 Compare February 7, 2026 10:12
public override void Write(Utf8JsonWriter writer, TestEnumParametersEnumQueryDoubleParameter testEnumParametersEnumQueryDoubleParameter, JsonSerializerOptions options)
{
writer.WriteStringValue(TestEnumParametersEnumQueryDoubleParameterValueConverter.ToJsonValue(testEnumParametersEnumQueryDoubleParameter).ToString());
writer.WriteNumberValue(TestEnumParametersEnumQueryDoubleParameterValueConverter.ToJsonValue(testEnumParametersEnumQueryDoubleParameter));
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot This changes the past behaviour.
The enum in c# looks like this for double/float formatted OpenAPI spec.

public enum TestEnumParametersEnumQueryDoubleParameter
    {
        /// <summary>
        /// Enum NUMBER_1_DOT_1 for value: 1.1
        /// </summary>
        NUMBER_1_DOT_1 = 1,

        /// <summary>
        /// Enum NUMBER_MINUS_1_DOT_2 for value: -1.2
        /// </summary>
        NUMBER_MINUS_1_DOT_2 = 2
    }

Enums in c# can only inherit from integers (long, int, ushort etc), not floating points. This was previously solved by always converting them to string manually, and then writing the string representation -1.2 into the json.

This is now completely lost by casting the enum value to double as it will be 2.0 instead of -1.2. So instead what we have to do probably is change the generator so that the ToJsonValue` actually returns the double values, instead of the enum underlying value which will be int32 always.

Fix this for all double enum files and templates in csharp generic host.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 45f833b3. The template now handles three categories of enums:

  1. Integer enums (int32/int64): Direct reader.GetInt32()/GetInt64() → cast to enum, ToJsonValue returns (int) value
  2. Floating-point enums (double/float/decimal): reader.GetDouble() etc. → .ToString(CultureInfo.InvariantCulture)FromStringOrDefault for matching, ToJsonValue returns actual values (1.1d, -1.2d) via switch-case instead of broken cast
  3. String enums: Unchanged reader.GetString() path

Added a DoubleEnum to the test spec with assertions verifying reader.GetDouble().ToString(...), return 1.1d;/return -1.2d;, and no return (double) value.

For floating-point enums (double, float, decimal), C# enums can't have
non-integral underlying types, so:
- ToJsonValue now returns actual enum values via switch-case instead of
  casting (which gave wrong values like 2.0 instead of -1.2)
- Read method uses reader.GetDouble() etc. and converts to string with
  InvariantCulture for matching via FromStringOrDefault
- Write method uses WriteNumberValue with the corrected ToJsonValue

Integer enums (int32, int64) still use direct cast as before.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Copilot AI requested a review from alexaka1 February 7, 2026 10:47
@alexaka1
Copy link
Owner

alexaka1 commented Feb 7, 2026

@copilot validate all changes in the PR conform to CONTRIBUTING.md or equivalent guidelines in the repo.

Copy link
Author

Copilot AI commented Feb 7, 2026

@copilot validate all changes in the PR conform to CONTRIBUTING.md or equivalent guidelines in the repo.

Validated against CONTRIBUTING.md — all guidelines are met:

  1. Separate branch (copilot/fix-enum-string-generation)
  2. Templates in modules/openapi-generator/src/main/resources/csharp/
  3. Tests added in modules/openapi-generator/src/test/java/org/openapitools/codegen/csharpnetcore/CSharpClientCodegenTest.java covering int32, int64, and double enums
  4. Test spec integer-enum.yaml in modules/openapi-generator/src/test/resources/3_0/csharp/ (follows existing naming convention)
  5. All 64 C# tests pass with no regressions
  6. Samples regenerated for all 17 affected generichost directories (74 files); verified remaining configs (AllOf, AnyOf, OneOf, UseDateTimeForDate, latest/*) don't have integer/double enums so no missed regenerations
  7. C# and Java style guides followed (consistent with existing code)
  8. LF line endings throughout (per core.autocrlf input recommendation)
  9. Meaningful commit messages
  10. No new vendor extensions added

Comment on lines 29 to 37
LongEnum:
enum:
- 0
- 1
type: integer
format: int64
x-enum-varnames:
- None
- Some
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot For int64 use an actual int64 value so we don't accidentally only support int32 values with int64 branch.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in b672dfd1. LongEnum now uses 2147483648 (int32 max + 1) and 9223372036854775807 (int64 max) to ensure values that exceed int32 range are properly handled. Test also asserts these values appear in the generated enum definition.

Comment on lines 120 to 124

int rawValue = reader.GetInt32();
OuterEnumInteger result = (OuterEnumInteger)rawValue;
return result;

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/net4.8/FormModels/src/Org.OpenAPITools/Model/RequiredClassRequiredNotnullableEnumIntegerOnly.cs, line 108:

Integer enum deserialization no longer validates that the value is defined in the enum, so invalid values now deserialize silently instead of throwing JsonException.

@@ -104,16 +104,9 @@ public class RequiredClassRequiredNotnullableEnumIntegerOnlyJsonConverter : Json - - throw new JsonException(); + int rawValue = reader.GetInt32(); + RequiredClassRequiredNotnullableEnumIntegerOnly result = (RequiredClassRequiredNotnullableEnumIntegerOnly)rawValue; + return result; }

Copy link
Author

Choose a reason for hiding this comment

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

Valid issue. Fixed in 6932a222. The root cause was the triple-negation mustache pattern {{^isFloat}}{{^isDouble}}{{^isDecimal}} which caused the Java Mustache engine to produce spurious blank lines and incorrect indentation in the generated C# code. Replaced with positive {{#isInteger}}/{{#isLong}} flags which correctly produce standalone lines that get stripped. Generated code now matches the original formatting.

Comment on lines 96 to 126
{{/isDecimal}}{{/isDouble}}{{/isFloat}}
{{#isFloat}}
{{#allowableValues}}
{{#enumVars}}
if (value == {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}})
return {{{value}}}f;

{{/enumVars}}
{{/allowableValues}}
throw new NotImplementedException($"Value could not be handled: '{value}'");
{{/isFloat}}
{{#isDouble}}
{{#allowableValues}}
{{#enumVars}}
if (value == {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}})
return {{{value}}}d;

{{/enumVars}}
{{/allowableValues}}
throw new NotImplementedException($"Value could not be handled: '{value}'");
{{/isDouble}}
{{#isDecimal}}
{{#allowableValues}}
{{#enumVars}}
if (value == {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}})
return {{{value}}}m;

{{/enumVars}}
{{/allowableValues}}
throw new NotImplementedException($"Value could not be handled: '{value}'");
{{/isDecimal}}
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/csharp/modelEnum.mustache, line 163:

Invariant-culture formatting in Read conflicts with FromStringOrDefault’s culture-sensitive numeric ToString(), so float/double/decimal enum deserialization can fail in non-US locales (e.g., "1.5" vs "1,5").

@@ -122,9 +154,32 @@ return result; + {{/isDecimal}}{{/isDouble}}{{/isFloat}} + {{#isFloat}} + string rawValue = reader.GetSingle().ToString(System.Globalization.CultureInfo.InvariantCulture); + {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? result = {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}ValueConverter.FromStringOrDefault(rawValue); + if (result != null)

Copy link
Author

Choose a reason for hiding this comment

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

Valid issue — same root cause as the other comment. Fixed in 6932a222. The triple-negation closing/opening tag pattern ({{/isDecimal}}{{/isDouble}}{{/isFloat}}{{#isFloat}}) on one line was not properly treated as a standalone line by the Java Mustache engine, producing blank lines in the output. Now using simple positive flags ({{#isInteger}}, {{#isLong}}, {{#isFloat}}, {{#isDouble}}, {{#isDecimal}}, {{#isString}}) which each get their own standalone lines and are properly stripped.

Use 2147483648 (int32 max + 1) and 9223372036854775807 (int64 max) to
ensure the int64 branch is properly exercised with values that exceed
int32 range.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Replace triple-negation pattern ({{^isFloat}}{{^isDouble}}{{^isDecimal}})
with positive flags ({{#isInteger}}, {{#isLong}}) for integer enum
branches. The triple-negation caused the Java Mustache engine to produce
spurious blank lines and incorrect indentation in the generated C# code.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Comment on lines 107 to 109
int rawValue = reader.GetInt32();
EnumTestEnumInteger result = (EnumTestEnumInteger)rawValue;
return result;
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/net9/NullReferenceTypes/src/Org.OpenAPITools/Model/OuterEnumInteger.cs, line 120:

Enum deserialization no longer validates that JSON integers map to defined enum members, allowing invalid values to deserialize silently.

@@ -117,16 +117,9 @@ public class OuterEnumIntegerJsonConverter : JsonConverter - return result.Value; - - throw new JsonException(); + int rawValue = reader.GetInt32(); + OuterEnumInteger result = (OuterEnumInteger)rawValue; + return result;

Please follow this pattern for all data types that we have changed.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 5233c7cb. All numeric enum types (int, long, float, double, decimal) now use the same validated Read pattern: read with correct numeric reader → .ToString(CultureInfo.InvariantCulture)FromStringOrDefault(rawValue)throw new JsonException() if invalid. This applies to both JsonConverter<T> and JsonConverter<T?> variants.

Use FromStringOrDefault for all numeric enum types (int, long, float,
double, decimal), not just float/double/decimal. This ensures invalid
values throw JsonException instead of being silently cast to the enum.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Copilot AI requested a review from alexaka1 February 7, 2026 13:05
Comment on lines 71 to 77
if (value.Equals("1.1"))
return TestEnumParametersEnumQueryDoubleParameter.NUMBER_1_DOT_1;

if (value.Equals("-1.2"))
return TestEnumParametersEnumQueryDoubleParameter.NUMBER_MINUS_1_DOT_2;

return null;
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/net4.8/FormModels/src/Org.OpenAPITools/Model/RequiredClassRequiredNotnullableEnumInteger.cs, line 108:

Invariant-culture formatting is compared against current-culture string literals in FromStringOrDefault, so deserialization can fail in cultures with non-Western digits or a different negative sign. Consider comparing integer values directly or using invariant culture in FromStringOrDefault.

@@ -104,9 +104,11 @@ public class RequiredClassRequiredNotnullableEnumIntegerJsonConverter : JsonConv - RequiredClassRequiredNotnullableEnumInteger result = (RequiredClassRequiredNotnullableEnumInteger)rawValue; - return result; + string rawValue = reader.GetInt32().ToString(System.Globalization.CultureInfo.InvariantCulture); + RequiredClassRequiredNotnullableEnumInteger? result = RequiredClassRequiredNotnullableEnumIntegerValueConverter.FromStringOrDefault(rawValue); + if (result != null) + return result.Value;

This applies to all enum datatypes.

Copy link
Author

Choose a reason for hiding this comment

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

I've verified the pattern and it's consistent across all enum datatypes (int, long, float, double, decimal). For all numeric types, the Read method:

  1. Reads with the correct numeric reader (GetInt32(), GetInt64(), GetDouble(), etc.)
  2. Converts to string via .ToString(CultureInfo.InvariantCulture)
  3. Validates via FromStringOrDefault(rawValue)
  4. Throws JsonException if invalid

Could you clarify what specific issue you're seeing? The FromStringOrDefault methods themselves were not changed by this PR — they're pre-existing template code. For integer enums they use (1).ToString() (no culture, but integers are culture-invariant), and for double enums they use string literals like "1.1" (which matches InvariantCulture output). Is the concern about:

  • The FromStringOrDefault comparison not using InvariantCulture explicitly?
  • A different formatting issue in the Read method pattern?
  • Something else in the generated output?

Remove System.Globalization.CultureInfo.InvariantCulture from all
.ToString() calls in the enum JsonConverter Read methods. This ensures
the culture used for ToString matches the culture used in
FromStringOrDefault comparisons (both now use system default culture).
Since the Write path outputs the numeric value directly, the JSON
output is always correct regardless of culture.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Copilot AI changed the title [BUG][csharp] Fix integer enum JSON converters using string operations instead of numeric [BUG][csharp] Fix integer/numeric enum JSON converters using string operations instead of numeric Feb 7, 2026
Comment on lines 306 to 307
writer.WriteStringValue({{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}.HasValue ? {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}.Value).ToString() : "null");
{{/isString}}
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
In @tools/openapi/templates/csharp/modelEnum.mustache around lines 305 - 307,
The template emits a quoted "null" string for nullable string enums because the
ternary fallback uses the literal "null" in the writer.WriteStringValue(...)
call; update the logic so when the nullable enum
({{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}})
HasValue is false the template emits writer.WriteNullValue() instead of a
WriteStringValue("null"). Replace the current ternary in the
writer.WriteStringValue(...) expression with a conditional that calls
WriteStringValue(...ToString()) when HasValue is true and calls
writer.WriteNullValue() when false (affecting the writer.WriteStringValue usage,
the ValueConverter.ToJsonValue(...) call, and the HasValue check in the
modelEnum.mustache template).

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 383ea387. The nullable Write method now uses a conditional: WriteStringValue(...) when HasValue is true, WriteNullValue() when false. This applies to both string and numeric enum types (numeric already had this pattern, now string does too).

Comment on lines 277 to 288
@@ -169,6 +285,7 @@
return result.Value;

throw new JsonException();
{{/isString}}
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
In @tools/openapi/templates/csharp/modelEnum.mustache around lines 277 - 288,
The NullableJsonConverter.Read implementation for string-backed enums should
detect a JSON null token before calling reader.GetString and return null rather
than throwing; update the isString branch in the NullableJsonConverter.Read
method to check reader.TokenType == JsonTokenType.Null (or check rawValue ==
null immediately after GetString) and return null when appropriate, then only
call ValueConverter.FromStringOrDefault(rawValue) for non-null rawValue so that
deserializing JSON null yields a null nullable enum instead of raising
JsonException.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 383ea387. The JsonTokenType.Null check is now applied to ALL enum types (including string) in the nullable converter Read method. Previously it was gated behind {{^isString}}, so string enums would fall through to reader.GetString() and then throw JsonException when the result was null. Now it returns null immediately for null tokens.

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot
In @tools/openapi/templates/csharp/modelEnum.mustache at line 24, The enum
declaration only checks vendorExtensions.x-enum-byte and misses the isLong flag,
causing 64-bit enums to default to int and overflow; update the template's enum
header (the line containing "{{>visibility}} enum
{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-enum-byte}}:
byte{{/vendorExtensions.x-enum-byte}}") to also check {{#isLong}} and emit ":
long" when true (in addition to keeping the existing x-enum-byte branch) so that
enums use long as the underlying type when isLong is set.

Comment on lines 282 to 336
File intEnumFile = files.get(Paths
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", "IntegerEnum.cs")
.toString()
);
assertNotNull(intEnumFile, "Could not find file for model: IntegerEnum");
assertFileContains(intEnumFile.toPath(),
"reader.GetInt32().ToString()",
"FromStringOrDefault(rawValue)",
"throw new JsonException()",
"writer.WriteNumberValue(",
"public static int ToJsonValue(IntegerEnum value)"
);
assertFileNotContains(intEnumFile.toPath(),
"reader.GetString()",
"writer.WriteStringValue("
);

// Verify long enum uses int64 reader with validation and actual int64 values
File longEnumFile = files.get(Paths
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", "LongEnum.cs")
.toString()
);
assertNotNull(longEnumFile, "Could not find file for model: LongEnum");
assertFileContains(longEnumFile.toPath(),
"reader.GetInt64().ToString()",
"FromStringOrDefault(rawValue)",
"throw new JsonException()",
"writer.WriteNumberValue(",
"public static long ToJsonValue(LongEnum value)",
"AboveInt32Max = 2147483648",
"Int64Max = 9223372036854775807"
);
assertFileNotContains(longEnumFile.toPath(),
"reader.GetString()",
"writer.WriteStringValue("
);

// Verify double enum reads numeric value and converts to string for matching, writes as number
File doubleEnumFile = files.get(Paths
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", "DoubleEnum.cs")
.toString()
);
assertNotNull(doubleEnumFile, "Could not find file for model: DoubleEnum");
assertFileContains(doubleEnumFile.toPath(),
"reader.GetDouble().ToString()",
"writer.WriteNumberValue(",
"public static double ToJsonValue(DoubleEnum value)",
"return 1.1d;",
"return -1.2d;"
);
assertFileNotContains(doubleEnumFile.toPath(),
"reader.GetString()",
"writer.WriteStringValue(",
"return (double) value"
);
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot test the datatype of the generated enum as well. Long and byte should have : byte or : long in them respectively...

…token in Read

Nullable string enum Write now calls WriteNullValue() when HasValue is
false, instead of writing the string literal "null".

Nullable enum Read (all types) now checks reader.TokenType ==
JsonTokenType.Null and returns null before attempting to read the value.
Previously this check was only applied to non-string enum types.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Copilot AI and others added 2 commits February 7, 2026 17:28
The enum declaration template now checks `{{#isLong}}` to emit `: long`
as the underlying type, preventing overflow for int64 enum values.

Added test assertions verifying:
- LongEnum has `: long` in its declaration
- IntegerEnum does NOT have `: long` or `: byte`

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Changes `enum Name: long` to `enum Name : long` (and same for `: byte`)
to match standard C# formatting.

Co-authored-by: alexaka1 <22166651+alexaka1@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants