Skip to content

[BUG] [CSharp] Discriminator mapping for polymorphie is ignored #22284

Open

Description

@pfeufferdaniel

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

I tested polymoprphie with with the charp generator default not library=RestSharp. In my open api description i have a discriminator mapping which is not applied correctly in the Deserializer of the BaseType
Type: ValueTypeBase

openapi-generator version

v7.17.0

OpenAPI declaration file content or url

openapi.json

Generation Details

config.yaml

additionalProperties:
  targetFramework: net9.0
  packageName: Rexroth.InduFlow.ApiClient
docker run --rm -v C:\100_Devel\induflow-example\:/local openapitools/openapi-generator-cli:v7.17.0 generate -i /local/code-generation/openapi.json -g csharp -o /local/lib/Rexroth.InduFlow.ApiClient -c /local/code-generation/config.yaml	
Output
Steps to reproduce

Generate as described and see the Model/ValueTypeBase.cs

public override ValueTypeBase Read(ref Utf8JsonReader utf8JsonReader, Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
        {
            int currentDepth = utf8JsonReader.CurrentDepth;

            if (utf8JsonReader.TokenType != JsonTokenType.StartObject && utf8JsonReader.TokenType != JsonTokenType.StartArray)
                throw new JsonException();

            JsonTokenType startingTokenType = utf8JsonReader.TokenType;

            Option<string?> kind = default;

            string? discriminator = ClientUtils.GetDiscriminator(utf8JsonReader, "kind");

            if (discriminator != null && discriminator.Equals("BooleanValue"))
                return JsonSerializer.Deserialize<BooleanValue>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value.");

            if (discriminator != null && discriminator.Equals("NumberValue"))
                return JsonSerializer.Deserialize<NumberValue>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value.");

            if (discriminator != null && discriminator.Equals("PhysicalValue"))
                return JsonSerializer.Deserialize<PhysicalValue>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value.");

            if (discriminator != null && discriminator.Equals("TextValue"))
                return JsonSerializer.Deserialize<TextValue>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value.");

            while (utf8JsonReader.Read())
            {
                if (startingTokenType == JsonTokenType.StartObject && utf8JsonReader.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReader.CurrentDepth)
                    break;

                if (startingTokenType == JsonTokenType.StartArray && utf8JsonReader.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReader.CurrentDepth)
                    break;

                if (utf8JsonReader.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReader.CurrentDepth - 1)
                {
                    string? localVarJsonPropertyName = utf8JsonReader.GetString();
                    utf8JsonReader.Read();

                    switch (localVarJsonPropertyName)
                    {
                        case "kind":
                            kind = new Option<string?>(utf8JsonReader.GetString()!);
                            break;
                        default:
                            break;
                    }
                }
            }

            if (!kind.IsSet)
                throw new ArgumentException("Property is required for class ValueTypeBase.", nameof(kind));

            if (kind.IsSet && kind.Value == null)
                throw new ArgumentNullException(nameof(kind), "Property is not nullable for class ValueTypeBase.");

            return new ValueTypeBase();
        }

discriminator check should be on "bool", "text", "physical" and "number"

Related issues/PRs
Suggest a fix

Activity

  1. wing328 commented on Nov 5, 2025

    @wing328
    Member
    library=RestSharp
    

    can you try restsharp library? I remember it has implementation for discriminator mapping but I could be wrong.

  2. pfeufferdaniel commented on Nov 7, 2025

    @pfeufferdaniel
    Author

    sorry for the late response i thought i got some notifications if some one replies....

    yes we tried restsharp and it do the mapping. But we had some other troubles with the Restsharp implmentation. And we like the default generator because it has less dependency.
    But do you know what will be the maintained better?

    But never the less we changed our Open Api description especially this parts

     "Property" : {
            "description" : "This class presents a property.\r\nA property could have different value types e.g. NumberValue, TextValue",
            "properties" : {
              "Name" : {
                "description" : "Property Name",
                "type" : "string"
              },
              "Type" : {
                "description" : "The optional type of a property",
                "nullable" : true,
                "type" : "string"
              },
              "Value" : {
                "$ref" : "#/components/schemas/PropertyValueComposition"
              }
            },
            "type" : "object"
          },
    ....
          "PropertyValueComposition" : {
            "oneOf" : [ {
              "$ref" : "#/components/schemas/BooleanValue"
            }, {
              "$ref" : "#/components/schemas/NumberValue"
            }, {
              "$ref" : "#/components/schemas/PhysicalValue"
            }, {
              "$ref" : "#/components/schemas/TextValue"
            } ],
            "discriminator" : {
              "mapping" : {
                "bool" : "#/components/schemas/BooleanValue",
                "number" : "#/components/schemas/NumberValue",
                "physical" : "#/components/schemas/PhysicalValue",
                "text" : "#/components/schemas/TextValue"
              },
              "propertyName" : "kind"
            },
          },
    ....
    "ValueTypeBase" : {
            "description" : "This is the base class for different value types.\r\nEach value type has a 'kind' property to discriminate between different types.",
            "properties" : {
              "kind" : {
                "description" : "The kind of value type",
                "type" : "string"
              }
            },
            "required" : [ "kind" ],
            "type" : "object"
          },
          "NumberValue" : {
            "allOf" : [ {
              "$ref" : "#/components/schemas/ValueTypeBase"
            }, {
              "description" : "The number value",
              "properties" : {
                "Value" : {
                  "description" : "The value e.g. '5.0'",
                  "format" : "double",
                  "type" : "number"
                }
              },
              "required" : [ "Value" ],
              "type" : "object"
            } ]
          },
          "PhysicalValue" : {
            "allOf" : [ {
              "$ref" : "#/components/schemas/ValueTypeBase"
            }, {
              "description" : "This class describes a physical value",
              "properties" : {
                "Value" : {
                  "description" : "The value e.g. '5.0'",
                  "format" : "double",
                  "type" : "number"
                },
                "Unit" : {
                  "description" : "The unit name e.g. 'centimeter'",
                  "nullable" : true,
                  "type" : "string"
                },
                "Quantity" : {
                  "description" : "The quantity name e.g. 'Length'",
                  "type" : "string"
                }
              },
              "required" : [ "Quantity", "Unit", "Value" ],
              "type" : "object"
            } ]
          },
          "TextValue" : {
            "allOf" : [ {
              "$ref" : "#/components/schemas/ValueTypeBase"
            }, {
              "description" : "This class describes a text value",
              "properties" : {
                "Value" : {
                  "description" : "The value e.g. '5.0'",
                  "type" : "string"
                }
              },
              "required" : [ "Value" ],
              "type" : "object"
            } ]
          }

    then it do the mapping:

        public override PropertyValueComposition Read(ref Utf8JsonReader utf8JsonReader, Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
            {
                int currentDepth = utf8JsonReader.CurrentDepth;
    
                if (utf8JsonReader.TokenType != JsonTokenType.StartObject && utf8JsonReader.TokenType != JsonTokenType.StartArray)
                    throw new JsonException();
    
                JsonTokenType startingTokenType = utf8JsonReader.TokenType;
    
                Option<string?> kind = default;
    
                BooleanValue? booleanValue = null;
                NumberValue? numberValue = null;
                PhysicalValue? physicalValue = null;
                TextValue? textValue = null;
    
                Utf8JsonReader utf8JsonReaderDiscriminator = utf8JsonReader;
                while (utf8JsonReaderDiscriminator.Read())
                {
                    if (startingTokenType == JsonTokenType.StartObject && utf8JsonReaderDiscriminator.TokenType == JsonTokenType.EndObject && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth)
                        break;
    
                    if (startingTokenType == JsonTokenType.StartArray && utf8JsonReaderDiscriminator.TokenType == JsonTokenType.EndArray && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth)
                        break;
    
                    if (utf8JsonReaderDiscriminator.TokenType == JsonTokenType.PropertyName && currentDepth == utf8JsonReaderDiscriminator.CurrentDepth - 1)
                    {
                        string? localVarJsonPropertyName = utf8JsonReaderDiscriminator.GetString();
                        utf8JsonReaderDiscriminator.Read();
                        if (localVarJsonPropertyName?.Equals("kind") ?? false)
                        {
                            string? discriminator = utf8JsonReaderDiscriminator.GetString();
                            if (discriminator?.Equals("bool") ?? false)
                            {
                                Utf8JsonReader utf8JsonReaderBooleanValue = utf8JsonReader;
                                booleanValue = JsonSerializer.Deserialize<BooleanValue>(ref utf8JsonReaderBooleanValue, jsonSerializerOptions);
                            }
                            if (discriminator?.Equals("number") ?? false)
                            {
                                Utf8JsonReader utf8JsonReaderNumberValue = utf8JsonReader;
                                numberValue = JsonSerializer.Deserialize<NumberValue>(ref utf8JsonReaderNumberValue, jsonSerializerOptions);
                            }
                            if (discriminator?.Equals("physical") ?? false)
                            {
                                Utf8JsonReader utf8JsonReaderPhysicalValue = utf8JsonReader;
                                physicalValue = JsonSerializer.Deserialize<PhysicalValue>(ref utf8JsonReaderPhysicalValue, jsonSerializerOptions);
                            }
                            if (discriminator?.Equals("text") ?? false)
                            {
                                Utf8JsonReader utf8JsonReaderTextValue = utf8JsonReader;
                                textValue = JsonSerializer.Deserialize<TextValue>(ref utf8JsonReaderTextValue, jsonSerializerOptions);
                            }
                        }
                    }
                }

    and works if the server still has polymorphie. But then the client has no Polymorphie....
    its not the best solution for us but something we can use.

  3. devhl-labs commented on Nov 8, 2025

    @devhl-labs
    Contributor

    Im tracking this already. I've got a branch i think targets this, its a tricky one though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions