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

AdditionalProperties JsonExtensionData repeated for subclasses in generated C# contracts #2818

Open
daniel-white opened this issue Apr 23, 2020 · 7 comments

Comments

@daniel-white
Copy link

daniel-white commented Apr 23, 2020

Given:

{
  "x-generator": "NSwag v13.4.2.0 (NJsonSchema v10.1.11.0 (Newtonsoft.Json v12.0.0.0))",
  "openapi": "3.0.0",
  "info": {
    "title": "My Title",
    "version": "1.0.0"
  },
  "paths": {
    "/WeatherForecast": {
      "get": {
        "tags": [
          "WeatherForecast"
        ],
        "operationId": "GetCurrentWeatherForecast",
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WeatherForecast"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": ""
          },
          "401": {
            "$ref": "#/components/responses/401",
            "description": ""
          },
          "403": {
            "$ref": "#/components/responses/403",
            "description": ""
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": ""
          },
          "500": {
            "$ref": "#/components/responses/500",
            "description": ""
          },
          "503": {
            "$ref": "#/components/responses/503",
            "description": ""
          }
        }
      },
      "post": {
        "tags": [
          "WeatherForecast"
        ],
        "operationId": "UploadWeatherForecast",
        "requestBody": {
          "x-name": "request",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WeatherForecast"
              }
            }
          },
          "required": true,
          "x-position": 1
        },
        "responses": {
          "200": {
            "description": ""
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": ""
          },
          "401": {
            "$ref": "#/components/responses/401",
            "description": ""
          },
          "403": {
            "$ref": "#/components/responses/403",
            "description": ""
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": ""
          },
          "500": {
            "$ref": "#/components/responses/500",
            "description": ""
          },
          "503": {
            "$ref": "#/components/responses/503",
            "description": ""
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "WeatherForecast": {
        "type": "object",
        "description": "Represents a weather forecast for the given date.",
        "properties": {
          "date": {
            "type": "string",
            "description": "The time of the forecast.",
            "format": "date-time"
          },
          "temperatureC": {
            "type": "integer",
            "description": "The temp in celsius.",
            "format": "int32"
          },
          "temperatureF": {
            "type": "integer",
            "description": "The temp.",
            "format": "int32"
          },
          "summary": {
            "type": "string",
            "description": "The description."
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        }
      },
      "ValidationProblemDetails": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProblemDetails"
          },
          {
            "type": "object",
            "properties": {
              "errors": {
                "type": "object",
                "nullable": true,
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ]
      }
    },
    "responses": {
      "400": {
        "description": "Bad Request",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblemDetails"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblemDetails"
            }
          }
        }
      },
      "401": {
        "description": "Unauthorized"
      },
      "403": {
        "description": "Forbidden"
      },
      "404": {
        "description": "Not Found",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "500": {
        "description": "Internal Server Error",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "503": {
        "description": "Service Unavailable",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      }
    },
    "parameters": {
      "AuthorizationHeaderParameter": {
        "name": "Authorization",
        "in": "header",
        "required": true,
        "description": "The HTTP Authorization request header contains the credentials to authenticate a user agent with a server (From MDN).",
        "schema": {
          "type": "string",
          "nullable": false
        }
      }
    }
  }
}
{
  "runtime": "NetCore31",
  "defaultVariables": null,
  "documentGenerator": {
    "aspNetCoreToOpenApi": {
      "project": "NSwagTestHarness.csproj",
      "msBuildProjectExtensionsPath": null,
      "configuration": "$(Configuration)",
      "targetFramework": null,
      "noBuild": true,
      "verbose": false,
      "workingDirectory": null,
      "requireParametersWithoutDefault": true,
      "apiGroupNames": null,
      "defaultPropertyNameHandling": "Default",
      "defaultReferenceTypeNullHandling": "Null",
      "defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
      "defaultResponseReferenceTypeNullHandling": "NotNull",
      "defaultEnumHandling": "String",
      "flattenInheritanceHierarchy": false,
      "generateKnownTypes": true,
      "generateEnumMappingDescription": false,
      "generateXmlObjects": false,
      "generateAbstractProperties": false,
      "generateAbstractSchemas": true,
      "ignoreObsoleteProperties": false,
      "allowReferencesWithProperties": false,
      "excludedTypeNames": [],
      "serviceHost": null,
      "serviceBasePath": null,
      "serviceSchemes": [],
      "infoTitle": "Transaction Service",
      "infoDescription": null,
      "infoVersion": null,
      "documentTemplate": null,
      "documentProcessorTypes": [],
      "operationProcessorTypes": [],
      "typeNameGeneratorType": null,
      "schemaNameGeneratorType": null,
      "contractResolverType": null,
      "serializerSettingsType": null,
      "useDocumentProvider": true,
      "documentName": "v1",
      "aspNetCoreEnvironment": "Development",
      "createWebHostBuilderMethod": null,
      "startupType": "NSwagTestHarness.Startup",
      "allowNullableBodyParameters": false,
      "output": "gen/test-v1.json",
      "outputType": "OpenApi3",
      "assemblyPaths": [],
      "assemblyConfig": null,
      "referencePaths": [],
      "useNuGetCache": false
    }
  },
  "codeGenerators": {
    "openApiToCSharpClient": {
      "clientBaseClass": "ApiClientBase",
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": true,
      "injectHttpClient": true,
      "disposeHttpClient": false,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "TestClientException",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": true,
      "useBaseUrl": false,
      "generateBaseUrlProperty": false,
      "generateSyncMethods": false,
      "exposeJsonSerializerSettings": false,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": true,
      "contractsNamespace": "NSwagTestHarness.Client",
      "contractsOutputFilePath": "../NSwagTestHarness.Client/gen/Contracts.g.cs",
      "parameterDateTimeFormat": "o",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "NSwagTestHarnessClient",
      "operationGenerationMode": "SingleClientFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": true,
      "generateJsonMethods": false,
      "enforceFlagEnums": false,
      "parameterArrayType": "System.Collections.Generic.IEnumerable",
      "parameterDictionaryType": "System.Collections.Generic.IDictionary",
      "responseArrayType": "System.Collections.Generic.ICollection",
      "responseDictionaryType": "System.Collections.Generic.IDictionary",
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": false,
      "responseClass": "NSwagTestHarnessResponse",
      "namespace": "NSwagTestHarness.Client",
      "requiredPropertiesMustBeDefined": true,
      "dateType": "System.DateTime",
      "jsonConverters": null,
      "anyType": "object",
      "dateTimeType": "System.DateTime",
      "timeType": "System.TimeSpan",
      "timeSpanType": "System.TimeSpan",
      "arrayType": "System.Collections.Generic.ICollection",
      "arrayInstanceType": "System.Collections.Generic.List",
      "dictionaryType": "System.Collections.Generic.IDictionary",
      "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
      "arrayBaseType": "System.Collections.Generic.List",
      "dictionaryBaseType": "System.Collections.Generic.Dictionary",
      "classStyle": "Poco",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [
        "Authorization",
        "Accept-Language"
      ],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedArrays": false,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "inlineNamedAny": false,
      "generateDtoTypes": true,
      "generateOptionalPropertiesAsNullable": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "../NSwagTestHarness.Client/gen/Client.g.cs"
    }
  }
}

The generated contracts for ProblemDetails and ValidationProblemDetails both generate AdditionalProperties which could be a problem with the properties conflicting:

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class ProblemDetails 
    {
        [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.Uri Type { get; set; }
    
        [Newtonsoft.Json.JsonProperty("title", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Title { get; set; }
    
        [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public int? Status { get; set; }
    
        [Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Detail { get; set; }
    
        [Newtonsoft.Json.JsonProperty("instance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.Uri Instance { get; set; }
    
        private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
    
        [Newtonsoft.Json.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties; }
            set { _additionalProperties = value; }
        }
    
    
    }
    
    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class ValidationProblemDetails : ProblemDetails
    {
        [Newtonsoft.Json.JsonProperty("errors", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.Collections.Generic.IDictionary<string, System.Collections.Generic.ICollection<string>> Errors { get; set; }
    
        private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
    
        [Newtonsoft.Json.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties; }
            set { _additionalProperties = value; }
        }
    
    
    }

My guess is that the disabling of CS0108 is hiding the warning.

#pragma warning disable 108 

Wouldn't it make sense that NJsonSchema or NSwag see the inherited property/configuration for JsonExtensionData and not generate it?

@daniel-white daniel-white changed the title AdditionalProperties JsonExtensionData repeated for subclasses in C# client AdditionalProperties JsonExtensionData repeated for subclasses in generated C# contracts Apr 23, 2020
@RicoSuter RicoSuter added this to the vNext milestone Apr 23, 2020
@RicoSuter
Copy link
Owner

This is a serious bug i’d say.

@daniel-white
Copy link
Author

It appears to be an issue with using AlwaysAllowAdditionalObjectProperties.

@RicoSuter
Copy link
Owner

AlwaysAllowAdditionalObjectProperties Is for the schema generator - and the schema is correct i’d say. The problem is the code generator: it should not generate the property if it already exists on the base class.

@RicoSuter
Copy link
Owner

But yes, if you have the option to disable AlwaysAllowAdditionalObjectProperties it might fix it, but not fix the root cause.

@cdimitroulas
Copy link

I believe this may be the root cause of some runtime errors I'm seeing in a generated C# client. The error is:

System.InvalidOperationException: The type 'Profiles.Api.ValidationProblemDetails' cannot have more than one
member that has the attribute 'System.Text.Json.Serialization.JsonExtensionDataAttribute'

I have one endpoint that returns ValidationProblemDetails whilst the rest return ProblemDetails

@sdecoodt
Copy link

sdecoodt commented Apr 8, 2022

I ran into this issue with problem details as wel.
It seems to have been partially fixed in #RicoSuter/NJsonSchema#1447 but that does not cover deeper inheritance layers

@RicoSuter I'm thinking of having a crack at fixing this, is there any specific branch i should start from?

@cdimitroulas
Copy link

FYI I was able to get around this by using the FlattenInheritanceHierarchy setting

      services.AddOpenApiDocument(settings =>
      {
        settings.FlattenInheritanceHierarchy = true;
        // ...rest of the settings
      });

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

No branches or pull requests

4 participants