Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract class cannot be instantiated #4197

Closed
GergoVandor opened this issue Oct 24, 2022 · 0 comments
Closed

Abstract class cannot be instantiated #4197

GergoVandor opened this issue Oct 24, 2022 · 0 comments

Comments

@GergoVandor
Copy link

GergoVandor commented Oct 24, 2022

Take this object structure:

public abstract class BaseClass
{
    public Guid Id { get; set; }
}

public sealed class ChildA
{
    public string Name { get; set; }
}

public sealed class ChildB
{
    public int Age { get; set; }
}

public sealed class ClassContainingBase
{
    public BaseClass BaseClass { get; set; }
}

If I configure the ClassContainingBase with fluent validator like this:
RuleFor(c => c.BaseClass).NotEmpty();

Add the appropriate x-abstract extensions and child schema into the swagger document, it will generate the following:

"BaseClass": {
        "required": [
          "$type"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "$type": {
            "type": "string",
            "description": "The discriminator."
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "$type",
          "mapping": {
            "ChildA": "#/components/schemas/ChildA",
            "ChildB": "#/components/schemas/ChildB"
          }
        },
        "x-abstract": true
      },
      "ChildA": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass"
          }
        ],
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ChildB": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass"
          }
        ],
        "properties": {
          "age": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "ClassContainingBase": {
        "required": [
          "baseClass"
        ],
        "type": "object",
        "properties": {
          "baseClass": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BaseClass"
              }
            ]
          }
        },
        "additionalProperties": false
      },

And this will generate the following c# client (regardless of the generate default values setting, Nswag v13.17.0.0, NJsonSchema v10.8.0):

[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "$type")]
    [JsonInheritanceAttribute("ChildA", typeof(ChildA))]
    [JsonInheritanceAttribute("ChildB", typeof(ChildB))]
    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public abstract partial class BaseClass
    {
        [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.Guid Id { get; set; }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class ChildA : BaseClass
    {
        [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Name { get; set; }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class ChildB : BaseClass
    {
        [Newtonsoft.Json.JsonProperty("age", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public int Age { get; set; }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.17.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class ClassContainingBase
    {
        [Newtonsoft.Json.JsonProperty("baseClass", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required]
        public BaseClass BaseClass { get; set; } = new BaseClass();

    }

As you can see in the generated ClassContainingBase it tries to instantiate BaseClass, which is clearly not valid.
So I'd expect that the generated client never tries to instantiate an abstract class, regardless of its required setting.

I took a look at the source code of NJsonSchema, and I think the problem is around NJsonSchema.CodeGeneration.CSharp.CSharpValueGenerator's GetDefaultValue method since if the schema.Type.IsObject is true, then it should also check if the property.IsAbstract is true, and if yes then it should return null

I also created a pull request into NJsonSchema: RicoSuter/NJsonSchema#1570

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

No branches or pull requests

2 participants