C# Model - oneOf of list and object is not deserialized correctly
- Dominant language
- C#
- Stars
- 3.8k
- Forks
- 333
- Avg merge
- 16h 29m
- Merged PRs (30d)
- 116
Description
I want to consume an API that has the following construct for properties (`OneOrManyItems` in this example) at quite a lot of places:
```json
{
"openapi": "3.0.3",
"info": {
"title": "Test",
"version": ""
},
"paths": {
"/test": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
},
"401": {
"description": "Unauthorized Request"
}
}
}
}
},
"components": {
"schemas": {
"Result": {
"properties": {
"OneOrManyItems": {
"oneOf": [
{
"type": "array",
"items": {
"$ref": "#/components/schemas/Item"
}
},
{
"$ref": "#/components/schemas/Item"
}
]
}
}
},
"Item": {
"properties": {
"Id": {
"type": "string"
}
}
}
}
}
}
```
Its basically a property (`OneOrManyItems`) that can be a list of objects or just one object (both with the same type `Item`).
When I now generate a client using this command: `kiota generate -d desciption.json -l CSharp`.
I get a model for the result that does not parse the json response correctly. It only parses it correctly for the case that it is a list of items not a single item.
The `Result_OneOrManyItems` class inside the `Result` looks something like this:
```cs
public class Result_OneOrManyItems : IComposedTypeWrapper, IParsable {
public List? Item { get; set; }
public ApiSdk.Models.Item? ResultOneOrManyItemsItem { get; set; }
public static Result_OneOrManyItems CreateFromDiscriminatorValue(IParseNode parseNode) {
_ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
var mappingValue = parseNode.GetChildNode("")?.GetStringValue();
var result = new Result_OneOrManyItems();
if("Item".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
result.ResultOneOrManyItemsItem = new ApiSdk.Models.Item();
}
else if(parseNode.GetCollectionOfObjectValues(ApiSdk.Models.Item.CreateFromDiscriminatorValue)?.ToList() is List itemValue) {
result.Item = itemValue;
}
return result;
}
public virtual IDictionary> GetFieldDeserializers() {
if(ResultOneOrManyItemsItem != null) {
return ResultOneOrManyItemsItem.GetFieldDeserializers();
}
return new Dictionary>();
}
public virtual void Serialize(ISerializationWriter writer) {
_ = writer ?? throw new ArgumentNullException(nameof(writer));
if(ResultOneOrManyItemsItem != null) {
writer.WriteObjectValue(null, ResultOneOrManyItemsItem);
}
else if(Item != null) {
writer.WriteCollectionOfObjectValues(null, Item);
}
}
}
```
When deserialized the `ResultOneOrManyItemsItem` property is always null and the `Item` is always non null but only contains something, if the parsed json is an array.
The problem seems to me that the `CreateFromDiscriminatorValue` function only looks at the discriminator value even if there is none and not at the type of the json that is being parsed.
The solution I found for my usecase looks like this:
```cs
public static Result_OneOrManyItems CreateFromDiscriminatorValue(IParseNode parseNode) {
_ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
var result = new Result_OneOrManyItems();
var list = parseNode.GetCollectionOfObjectValues(ApiSdk.Models.Item.CreateFromDiscriminatorValue)?.ToList() as List;
if((list.Count ?? 0) == 0) {
result.ResultOneOrManyItemsItem = new ApiSdk.Models.Item();
}
else {
result.Item = list;
}
return result;
}
```
This is far from ideal because it does not allow empty lists and does not use the real underling type. I also have to touch a lot of generated code for this solution which is not convenient and error prone.
This Issue is probably similar to #2338 but it wasn't 100% my use case, hence I created a new issue.
I also saw a similar API description in this issue #3963 but the problem was a different one.
If this is not fixable in the short term is there a better workaround than adjusting all implementations of `CreateFromDiscriminatorValue` by hand?
Contributor guide
Assessment
This issue has not been assessed yet.