Skip to content

[BUG] [C#] Using "disallowAdditionalPropertiesIfNotPresent": false is not equivalent to setting "additionalProperties":true #13142

@laura-rodriguez

Description

@laura-rodriguez

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • 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'm using the csharp-netcore generator and running into issues with additional properties. I have a model with some fixed properties, and you could add additional dynamic properties. When in the spec I set additionalProperties: true for that specific model, the generated class extends from a dictionary which causes issues with json serialization. However, when I remove additionalProperties: true from the spec and use "disallowAdditionalPropertiesIfNotPresent": false the generated model looks as I expected; The generated class doesn't extend from a dictionary, and an AdditionalProperties property is generated. Why disallowAdditionalPropertiesIfNotPresent:false is not equivalent to explicitly set additionalProperties:true?

Using "disallowAdditionalPropertiesIfNotPresent": false

Spec YAML:

UserProfile:
      type: object
      properties:
        city:
          type: string
          maxLength: 128
          nullable: true
...

Debug Model metadata

  "importPath" : "Model.UserProfile",
  "model" : {
    "anyOf" : [ ],
    "oneOf" : [ ],
    "allOf" : [ ],
    "name" : "UserProfile",
    "classname" : "UserProfile",
    "classVarName" : "UserProfile",
    "modelJson" : "{\r\n  \"type\" : \"object\",\r\n  \"properties\" : {\r\n    \"city\" : {\r\n      \"maxLength\" : 128,\r\n      \"type\" : \"string\",\r\n      \"nullable\" : true\r\n    } }\r\n}",
    "dataType" : "Object", !!!  NOTE this
    "classFilename" : "UserProfile",
    "isAlias" : false,
    "isString" : false,
    "isInteger" : false,
    "isLong" : false,
    "isNumber" : false,
    "isNumeric" : false,
    "isFloat" : false,
    "isDouble" : false,
    "isDate" : false,
    "isDateTime" : false,
    "isDecimal" : false,
    "isShort" : false,
    "isUnboundedInteger" : false,
    "isPrimitiveType" : false,
    "isBoolean" : false,
    "additionalPropertiesIsAnyType" : true, !!!  NOTE this

Generated class

    /// <summary>
    /// Template: ModelGeneric
    /// UserProfile
    /// </summary>
    [DataContract(Name = "UserProfile")]
    public partial class UserProfile : IEquatable<UserProfile>
    {
        
        /// <summary>
        /// Gets or Sets City
        /// </summary>
        [DataMember(Name = "city", EmitDefaultValue = true)]
        public string City { get; set; }

        /// <summary>
        /// Gets or Sets additional properties
        /// </summary>
        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties { get; set; }

Using additionalProperties: true

spec YAML

 UserProfile:
      type: object
      additionalProperties: true
      properties:
        city:
          type: string
          maxLength: 128
          nullable: true

Debug Model Metadata

 "model" : {
    "parent" : "Dictionary<String, Object>", !!! NOTE this
    "anyOf" : [ ],
    "oneOf" : [ ],
    "allOf" : [ ],
    "name" : "UserProfile",
    "classname" : "UserProfile",
    "classVarName" : "UserProfile",
    "modelJson" : "{\r\n  \"type\" : \"object\",\r\n  \"properties\" : {\r\n    \"city\" : {\r\n      \"maxLength\" : 128,\r\n      \"type\" : \"string\",\r\n      \"nullable\" : true\r\n    },\r\n  \"additionalProperties\" : true\r\n}",
    "dataType" : "Dictionary", !!! NOTE this
    "classFilename" : "UserProfile",
    "isAlias" : false,
    "isString" : false,
    "isInteger" : false,
    "isLong" : false,
    "isNumber" : false,
    "isNumeric" : false,
    "isFloat" : false,
    "isDouble" : false,
    "isDate" : false,
    "isDateTime" : false,
    "isDecimal" : false,
    "isShort" : false,
    "isUnboundedInteger" : false,
    "isPrimitiveType" : false,
    "isBoolean" : false,
    "additionalPropertiesIsAnyType" : true, !!! NOTE this

Generated class

 /// <summary>
    /// Template: ModelGeneric
    /// UserProfile
    /// </summary>
    [DataContract(Name = "UserProfile")]
    public partial class UserProfile : Dictionary<String, Object>, IEquatable<UserProfile> /// It now extends from Dictionary
    {
        
        /// <summary>
        /// Gets or Sets City
        /// </summary>
        [DataMember(Name = "city", EmitDefaultValue = true)]
        public string City { get; set; }
       
         /// <summary>
        /// Gets or Sets additional properties
        /// </summary>
        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties { get; set; }

Is there any reason I don't get the same output for equivalent configuration?

openapi-generator version

6.0.1

OpenAPI declaration file content or URL

PRovided above

Generation Details

openapi-generator-cli generate -g csharp-netcore --skip-validate-spec

openapi-generator-cli version
6.0.0-beta

Steps to reproduce

Run >openapi-generator-cli generate -g csharp-netcore --skip-validate-spec

Related issues/PRs

#9798
#9727
#7396

Suggest a fix

I would expect equivalent output for equivalent spec configuration. If I have a model with fixed properties AND additionalProperties:true the generated model should not extend from Dictionary since it causes serialization issues. Note that using "disallowAdditionalPropertiesIfNotPresent": false only and not additionalProperties:true behaves as expected.

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