[BUG] [C#] Optional parameters prevent deserialization
Description
I'm using version 7.5.0 of the generator and I'm generating C# classes using the generichost library setting.
Suppose I have the following OpenAPI component schema:
"UserDiscount": {
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"$ref": "#/components/schemas/uuid"
},
"name": {
"type": "string"
}
"markup": {
"type": "number",
"format": "decimal"
}
}
}
It will then generate the following C# class:
public partial class UserDiscount : IValidatableObject
{
[JsonConstructor]
public UserDiscount(string name, Guid id, Option<decimal?> discount = default)
{
Name = name;
Id = id;
DiscountOption = discount;
OnCreated();
}
partial void OnCreated();
/// <summary>
/// Gets or Sets BrandCode
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or Sets Id
/// </summary>
[JsonPropertyName("id")]
public Guid Id { get; set; }
/// <summary>
/// Used to track the state of Discount
/// </summary>
[JsonIgnore]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public Option<decimal?> DiscountOption { get; private set; }
/// <summary>
/// Gets or Sets Discount
/// </summary>
[JsonPropertyName("discount")]
public decimal? Discount { get { return this.DiscountOption; } set { this.DiscountOption = new(value); } }
/// <summary>
/// Gets or Sets additional properties
/// </summary>
[JsonExtensionData]
public Dictionary<string, JsonElement> AdditionalProperties { get; } = new Dictionary<string, JsonElement>();
/// <summary>
/// Returns the string presentation of the object
/// </summary>
/// <returns>String presentation of the object</returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("class CategoryInfo {\n");
sb.Append(" Name: ").Append(Name).Append("\n");
sb.Append(" Id: ").Append(Id).Append("\n");
sb.Append(" Discount: ").Append(Discount).Append("\n");
sb.Append(" AdditionalProperties: ").Append(AdditionalProperties).Append("\n");
sb.Append("}\n");
return sb.ToString();
}
/// <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(ValidationContext validationContext)
{
yield break;
}
}
But, when trying to deserialize some data like this:
var data = @"{
""name"": ""someName"",
""id"": ""527bce01-4b7a-41ed-b51d-5608acb1549c"",
""discount"": 4
}";
JsonSerializer.Deserialize<UserDiscount>(data).Dump();
I get the following error:
InvalidOperationException: Each parameter in the deserialization constructor on type 'UserDiscount' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.
The problem is that the discount parameter of the constructor is of type Option<decimal?> while the discount property is of type decimal?.
If I rename the constructor parameter to discountOption it works, because it maps to a property with the same name and type.
Or if I change the constructor parameter type to decimal? it works as well, because that also maps to a property with the same name and type.
I think the constructor parameter type does not need to be Option<decimal?> but can simply be decimal? since it will be implicitly converted anyway.
openapi-generator version
7.5.0
Why does it generate a Option<decimal?> anyways?
It does that if it is both not required and nullable. I see in the json you gave here, the field is called markup, yet the C# property is called discount. Can you please verify what you have is not working? When you de/serialize, ensure you're using the json options as well.
Yeah, I simplified the demo code a bit and removed the wrong name. It should have been discount, I've updated it now.
Your comment about the json options pointed me in the right direction! I totally missed that I needed the JsonConverter that's generated as well. 😖 After registrering that the serialization works fine.
Thanks for your help. I'll close this ticket.
For anyone else interested, this is the correct way te deserialize:
var serializeOptions = new JsonSerializerOptions
{
Converters =
{
new UserDiscountJsonConverter()
}
};
JsonSerializer.Deserialize<UserDiscount>(data, serializeOptions).Dump();
Yes but the options are already instantiated and in the service provider. You don't have to create another instance.