You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An API vendor has an API that has a schema that specifies both (explicit) properties and additional properties.
This causes a C# model to be generated that inherits from Dictionary<string, object> and a property of the same type.
It seems that the JSON de-serializer can not handle this situation, as the properties are left blank/empty while the dictionary (where the model inherited from) is filled with data.
This seems like sort of documented behavior of the Newtonsoft.Json serializer. At least for serializing, nothing is mentioned about deserializing.
.NET dictionaries (types that inherit from IDictionary) are converted to JSON objects. Note that only the dictionary name/values will be written to the JSON object when serializing, and properties on the JSON object will be added to the dictionary's name/values when deserializing. Additional members on the .NET dictionary are ignored during serialization.
When serializing a dictionary, the keys of the dictionary are converted to strings and used as the JSON object property names. The string written for a key can be customized by either overriding ToString() for the key type or by implementing a TypeConverter. A TypeConverter will also support converting a custom string back again when deserializing a dictionary.
JsonDictionaryAttribute has options on it to customize the JsonConverter, type name handling, and reference handling that are applied to collection items.
When deserializing, if a member is typed as the interface IDictionary<TKey, TValue> then it will be deserialized as a Dictionary<TKey, TValue>.
/* * Repro minimal API * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 0.0.1 * Generated by: https://github.com/openapitools/openapi-generator.git */usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.Collections.ObjectModel;usingSystem.Linq;usingSystem.IO;usingSystem.Runtime.Serialization;usingSystem.Text;usingSystem.Text.RegularExpressions;usingNewtonsoft.Json;usingNewtonsoft.Json.Converters;usingNewtonsoft.Json.Linq;usingSystem.ComponentModel.DataAnnotations;usingOpenAPIDateConverter=Tls.Clients.Gbo.Client.OpenAPIDateConverter;namespaceTls.Clients.Gbo.Model{/// <summary>/// JSON schema for GetSomeSchema/// </summary>[DataContract(Name="GetSomeSchema")]publicpartialclassGetSomeSchema:Dictionary<String,Object>,IEquatable<GetSomeSchema>,IValidatableObject{/// <summary>/// Initializes a new instance of the <see cref="GetSomeSchema" /> class./// </summary>[JsonConstructorAttribute]protectedGetSomeSchema(){this.AdditionalProperties=newDictionary<string,object>();}/// <summary>/// Initializes a new instance of the <see cref="GetSomeSchema" /> class./// </summary>/// <param name="someKey">Some key (required).</param>/// <param name="someValue">Some value (required).</param>/// <param name="someList">List of complex items (required).</param>publicGetSomeSchema(stringsomeKey=default(string),intsomeValue=default(int),List<SomeListItem>someList=default(List<SomeListItem>)):base(){// to ensure "someKey" is required (not null)this.SomeKey=someKey??thrownewArgumentNullException("someKey is a required property for GetSomeSchema and cannot be null");this.SomeValue=someValue;// to ensure "someList" is required (not null)this.SomeList=someList??thrownewArgumentNullException("someList is a required property for GetSomeSchema and cannot be null");this.AdditionalProperties=newDictionary<string,object>();}/// <summary>/// Some key/// </summary>/// <value>Some key</value>[DataMember(Name="someKey",IsRequired=true,EmitDefaultValue=false)]publicstringSomeKey{get;set;}/// <summary>/// Some value/// </summary>/// <value>Some value</value>[DataMember(Name="someValue",IsRequired=true,EmitDefaultValue=false)]publicintSomeValue{get;set;}/// <summary>/// List of complex items/// </summary>/// <value>List of complex items</value>[DataMember(Name="someList",IsRequired=true,EmitDefaultValue=false)]publicList<SomeListItem>SomeList{get;set;}/// <summary>/// Gets or Sets additional properties/// </summary>[JsonExtensionData]publicIDictionary<string,object>AdditionalProperties{get;set;}/// <summary>/// Returns the string presentation of the object/// </summary>/// <returns>String presentation of the object</returns>publicoverridestringToString(){varsb=newStringBuilder();sb.Append("class GetSomeSchema {\n");sb.Append(" ").Append(base.ToString().Replace("\n","\n ")).Append("\n");sb.Append(" SomeKey: ").Append(SomeKey).Append("\n");sb.Append(" SomeValue: ").Append(SomeValue).Append("\n");sb.Append(" SomeList: ").Append(SomeList).Append("\n");sb.Append(" AdditionalProperties: ").Append(AdditionalProperties).Append("\n");sb.Append("}\n");returnsb.ToString();}/// <summary>/// Returns the JSON string presentation of the object/// </summary>/// <returns>JSON string presentation of the object</returns>publicstringToJson(){returnNewtonsoft.Json.JsonConvert.SerializeObject(this,Newtonsoft.Json.Formatting.Indented);}/// <summary>/// Returns true if objects are equal/// </summary>/// <param name="input">Object to be compared</param>/// <returns>Boolean</returns>publicoverrideboolEquals(objectinput){returnthis.Equals(inputasGetSomeSchema);}/// <summary>/// Returns true if GetSomeSchema instances are equal/// </summary>/// <param name="input">Instance of GetSomeSchema to be compared</param>/// <returns>Boolean</returns>publicboolEquals(GetSomeSchemainput){if(input==null)returnfalse;returnbase.Equals(input)&&(this.SomeKey==input.SomeKey||(this.SomeKey!=null&&this.SomeKey.Equals(input.SomeKey)))&&base.Equals(input)&&(this.SomeValue==input.SomeValue||this.SomeValue.Equals(input.SomeValue))&&base.Equals(input)&&(this.SomeList==input.SomeList||this.SomeList!=null&&input.SomeList!=null&&this.SomeList.SequenceEqual(input.SomeList))&&(this.AdditionalProperties.Count==input.AdditionalProperties.Count&&!this.AdditionalProperties.Except(input.AdditionalProperties).Any());}/// <summary>/// Gets the hash code/// </summary>/// <returns>Hash code</returns>publicoverrideintGetHashCode(){unchecked// Overflow is fine, just wrap{inthashCode=base.GetHashCode();if(this.SomeKey!=null)hashCode=hashCode*59+this.SomeKey.GetHashCode();hashCode=hashCode*59+this.SomeValue.GetHashCode();if(this.SomeList!=null)hashCode=hashCode*59+this.SomeList.GetHashCode();if(this.AdditionalProperties!=null)hashCode=hashCode*59+this.AdditionalProperties.GetHashCode();returnhashCode;}}/// <summary>/// To validate all properties of the instance/// </summary>/// <param name="validationContext">Validation context</param>/// <returns>Validation Result</returns>IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult>IValidatableObject.Validate(ValidationContextvalidationContext){yieldbreak;}}}
SomeListItem.cs
/* * Repro minimal API * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 0.0.1 * Generated by: https://github.com/openapitools/openapi-generator.git */usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.Collections.ObjectModel;usingSystem.Linq;usingSystem.IO;usingSystem.Runtime.Serialization;usingSystem.Text;usingSystem.Text.RegularExpressions;usingNewtonsoft.Json;usingNewtonsoft.Json.Converters;usingNewtonsoft.Json.Linq;usingSystem.ComponentModel.DataAnnotations;usingOpenAPIDateConverter=Tls.Clients.Gbo.Client.OpenAPIDateConverter;namespaceTls.Clients.Gbo.Model{/// <summary>/// Some list item object/// </summary>[DataContract(Name="SomeListItem")]publicpartialclassSomeListItem:Dictionary<String,Object>,IEquatable<SomeListItem>,IValidatableObject{/// <summary>/// Initializes a new instance of the <see cref="SomeListItem" /> class./// </summary>/// <param name="someChildKey">List item key..</param>/// <param name="someChildValue">List item value..</param>publicSomeListItem(stringsomeChildKey=default(string),intsomeChildValue=default(int)):base(){this.SomeChildKey=someChildKey;this.SomeChildValue=someChildValue;this.AdditionalProperties=newDictionary<string,object>();}/// <summary>/// List item key./// </summary>/// <value>List item key.</value>[DataMember(Name="someChildKey",EmitDefaultValue=false)]publicstringSomeChildKey{get;set;}/// <summary>/// List item value./// </summary>/// <value>List item value.</value>[DataMember(Name="someChildValue",EmitDefaultValue=false)]publicintSomeChildValue{get;set;}/// <summary>/// Gets or Sets additional properties/// </summary>[JsonExtensionData]publicIDictionary<string,object>AdditionalProperties{get;set;}/// <summary>/// Returns the string presentation of the object/// </summary>/// <returns>String presentation of the object</returns>publicoverridestringToString(){varsb=newStringBuilder();sb.Append("class SomeListItem {\n");sb.Append(" ").Append(base.ToString().Replace("\n","\n ")).Append("\n");sb.Append(" SomeChildKey: ").Append(SomeChildKey).Append("\n");sb.Append(" SomeChildValue: ").Append(SomeChildValue).Append("\n");sb.Append(" AdditionalProperties: ").Append(AdditionalProperties).Append("\n");sb.Append("}\n");returnsb.ToString();}/// <summary>/// Returns the JSON string presentation of the object/// </summary>/// <returns>JSON string presentation of the object</returns>publicstringToJson(){returnNewtonsoft.Json.JsonConvert.SerializeObject(this,Newtonsoft.Json.Formatting.Indented);}/// <summary>/// Returns true if objects are equal/// </summary>/// <param name="input">Object to be compared</param>/// <returns>Boolean</returns>publicoverrideboolEquals(objectinput){returnthis.Equals(inputasSomeListItem);}/// <summary>/// Returns true if SomeListItem instances are equal/// </summary>/// <param name="input">Instance of SomeListItem to be compared</param>/// <returns>Boolean</returns>publicboolEquals(SomeListIteminput){if(input==null)returnfalse;returnbase.Equals(input)&&(this.SomeChildKey==input.SomeChildKey||(this.SomeChildKey!=null&&this.SomeChildKey.Equals(input.SomeChildKey)))&&base.Equals(input)&&(this.SomeChildValue==input.SomeChildValue||this.SomeChildValue.Equals(input.SomeChildValue))&&(this.AdditionalProperties.Count==input.AdditionalProperties.Count&&!this.AdditionalProperties.Except(input.AdditionalProperties).Any());}/// <summary>/// Gets the hash code/// </summary>/// <returns>Hash code</returns>publicoverrideintGetHashCode(){unchecked// Overflow is fine, just wrap{inthashCode=base.GetHashCode();if(this.SomeChildKey!=null)hashCode=hashCode*59+this.SomeChildKey.GetHashCode();hashCode=hashCode*59+this.SomeChildValue.GetHashCode();if(this.AdditionalProperties!=null)hashCode=hashCode*59+this.AdditionalProperties.GetHashCode();returnhashCode;}}/// <summary>/// To validate all properties of the instance/// </summary>/// <param name="validationContext">Validation context</param>/// <returns>Validation Result</returns>IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult>IValidatableObject.Validate(ValidationContextvalidationContext){yieldbreak;}}}
Steps to reproduce
Run the command (either in powershell or windows terminal).
Then check out the output file GetSomeSchema.cs and SomeListItem.
Notice that both objects inherit from Dictionary which is causing issues with the Newtonsoft.Json serializer.
I've not been able to create a full repro project, as this minimal repo is from a 1.4 megabyte API definition. If needed I can create a full repro project.
It seems that what is allowed in the OpenApi definition, a schema with explicit properties and additional properties, is not properly generating C# schema objects that can be handled by the used (de)serializer.
This is probably resolved by not having the schema object/class inherit Dictionary<string, object> any more. The specific properties are already there, including a property of type Dictionary<string, object> to store all properties on.
Description
An API vendor has an API that has a schema that specifies both (explicit) properties and additional properties.
This causes a C# model to be generated that inherits from
Dictionary<string, object>
and a property of the same type.It seems that the JSON de-serializer can not handle this situation, as the properties are left blank/empty while the dictionary (where the model inherited from) is filled with data.
This seems like sort of documented behavior of the Newtonsoft.Json serializer. At least for serializing, nothing is mentioned about deserializing.
See: https://www.newtonsoft.com/json/help/html/SerializationGuide.htm
openapi-generator version
Open API 5.1.1
https://github.com/OpenAPITools/openapi-generator/releases/tag/v5.1.1
OpenAPI declaration file content or url
repro.yml:
Generation Details
Input:
Powershell command:
config.yml:
Output:
GetSomeSchema.cs:
SomeListItem.cs
Steps to reproduce
Run the command (either in powershell or windows terminal).
Then check out the output file GetSomeSchema.cs and SomeListItem.
Notice that both objects inherit from Dictionary which is causing issues with the Newtonsoft.Json serializer.
I've not been able to create a full repro project, as this minimal repo is from a 1.4 megabyte API definition. If needed I can create a full repro project.
Related issues/PRs
https://stackoverflow.com/questions/31099396/serialize-class-that-inherits-dictionary-is-not-serializing-properties
https://stackoverflow.com/questions/14893614/how-to-serialize-a-dictionary-as-part-of-its-parent-object-using-json-net
Suggest a fix
It seems that what is allowed in the OpenApi definition, a schema with explicit properties and additional properties, is not properly generating C# schema objects that can be handled by the used (de)serializer.
This is probably resolved by not having the schema object/class inherit Dictionary<string, object> any more. The specific properties are already there, including a property of type Dictionary<string, object> to store all properties on.
Not sure if this only needs a (breaking?) change on the mustache file or a more advanced modification.
Think it is generated by this file: https://github.com/OpenAPITools/openapi-generator/blob/e9fa93688617d0cb602c5805c48d80750d570289/modules/openapi-generator/src/main/resources/csharp-netcore/modelGeneric.mustache
Bug Report Checklist
The text was updated successfully, but these errors were encountered: