[C#] Bug: OneOf used inside other OneOf causes duplicated fields in composed type
When I generate a client with the following schemas (a OneOf within a OneOf) it duplicates the properties for inner OneOf model.
This is an example of the input I give to Kiota:
{
"openapi": "3.0.1",
"info": {
"title": "Umbraco Delivery API",
"description": "",
"version": "Latest"
},
"paths": {
"/umbraco/delivery/api/v1/content/item/{path}": {
"get": {
"tags": ["Content"],
"operationId": "GetContentItemByPath",
"parameters": [
{
"name": "path",
"in": "path",
"required": true,
"schema": {
"type": "string",
"default": ""
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResponseModelBase"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ElementModel1": {
"type": "object",
"properties": {
"contentType": {
"type": "string",
"readOnly": true
}
},
"additionalProperties": false
},
"ElementModel2": {
"type": "object",
"properties": {
"contentType": {
"type": "string",
"readOnly": true
}
},
"additionalProperties": false
},
"ResponseModel2": {
"type": "object",
"properties": {
"contentType": {
"type": "string",
"readOnly": true
},
"first": {
"$ref": "#/components/schemas/ElementModelBase"
},
"second": {
"$ref": "#/components/schemas/ElementModelBase"
},
"third": {
"$ref": "#/components/schemas/ElementModelBase"
}
},
"additionalProperties": false
},
"ResponseModel1": {
"type": "object",
"properties": {
"contentType": {
"type": "string",
"readOnly": true
},
"first": {
"$ref": "#/components/schemas/ElementModelBase"
},
"second": {
"$ref": "#/components/schemas/ElementModelBase"
},
"third": {
"$ref": "#/components/schemas/ElementModelBase"
}
},
"additionalProperties": false
},
"ResponseModelBase": {
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/ResponseModel1"
},
{
"$ref": "#/components/schemas/ResponseModel2"
}
],
"additionalProperties": false,
"discriminator": {
"propertyName": "contentType",
"mapping": {
"a": "#/components/schemas/ResponseModel1",
"b": "#/components/schemas/ResponseModel2"
}
}
},
"ElementModelBase": {
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/ElementModel1"
},
{
"$ref": "#/components/schemas/ElementModel2"
}
],
"additionalProperties": false,
"discriminator": {
"propertyName": "contentType",
"mapping": {
"c": "#/components/schemas/ElementModel1",
"d": "#/components/schemas/ElementModel2"
}
}
}
}
}
}
And this is the generated ElementModelBase:
// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace Namespace.Models {
/// <summary>
/// Composed type wrapper for classes <see cref="ElementModel1"/>, <see cref="ElementModel2"/>
/// </summary>
public class ElementModelBase : IComposedTypeWrapper, IParsable
{
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModel1 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModel1 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModel2 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModel2 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModelBaseElementModel1 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModelBaseElementModel1 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModelBaseElementModel10 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModelBaseElementModel10 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModelBaseElementModel11 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModelBaseElementModel11 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModelBaseElementModel12 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModelBaseElementModel12 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModelBaseElementModel13 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModelBaseElementModel13 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModelBaseElementModel2 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModelBaseElementModel2 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModelBaseElementModel20 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModelBaseElementModel20 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModelBaseElementModel21 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModelBaseElementModel21 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModelBaseElementModel22 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModelBaseElementModel22 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModelBaseElementModel23 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModelBaseElementModel23 { get; set; }
#endif
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="ElementModelBase"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static ElementModelBase CreateFromDiscriminatorValue(IParseNode parseNode)
{
_ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
var mappingValue = parseNode.GetChildNode("contentType")?.GetStringValue();
var result = new ElementModelBase();
if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModel1 = new Namespace.Models.ElementModel1();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModel2 = new Namespace.Models.ElementModel2();
}
else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel1 = new Namespace.Models.ElementModel1();
}
else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel10 = new Namespace.Models.ElementModel1();
}
else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel11 = new Namespace.Models.ElementModel1();
}
else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel12 = new Namespace.Models.ElementModel1();
}
else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel13 = new Namespace.Models.ElementModel1();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel2 = new Namespace.Models.ElementModel2();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel20 = new Namespace.Models.ElementModel2();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel21 = new Namespace.Models.ElementModel2();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel22 = new Namespace.Models.ElementModel2();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModelBaseElementModel23 = new Namespace.Models.ElementModel2();
}
return result;
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary<string, Action<IParseNode>></returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
if(ElementModel1 != null)
{
return ElementModel1.GetFieldDeserializers();
}
else if(ElementModel2 != null)
{
return ElementModel2.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel1 != null)
{
return ElementModelBaseElementModel1.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel10 != null)
{
return ElementModelBaseElementModel10.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel11 != null)
{
return ElementModelBaseElementModel11.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel12 != null)
{
return ElementModelBaseElementModel12.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel13 != null)
{
return ElementModelBaseElementModel13.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel2 != null)
{
return ElementModelBaseElementModel2.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel20 != null)
{
return ElementModelBaseElementModel20.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel21 != null)
{
return ElementModelBaseElementModel21.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel22 != null)
{
return ElementModelBaseElementModel22.GetFieldDeserializers();
}
else if(ElementModelBaseElementModel23 != null)
{
return ElementModelBaseElementModel23.GetFieldDeserializers();
}
return new Dictionary<string, Action<IParseNode>>();
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
_ = writer ?? throw new ArgumentNullException(nameof(writer));
if(ElementModel1 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModel1);
}
else if(ElementModel2 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModel2);
}
else if(ElementModelBaseElementModel1 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel1);
}
else if(ElementModelBaseElementModel10 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel10);
}
else if(ElementModelBaseElementModel11 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel11);
}
else if(ElementModelBaseElementModel12 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel12);
}
else if(ElementModelBaseElementModel13 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel13);
}
else if(ElementModelBaseElementModel2 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel2);
}
else if(ElementModelBaseElementModel20 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel20);
}
else if(ElementModelBaseElementModel21 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel21);
}
else if(ElementModelBaseElementModel22 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel22);
}
else if(ElementModelBaseElementModel23 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel23);
}
}
}
}
Generated using:
kiota generate --openapi swagger_problem.json -l csharp -n Namespace -o DeliveryApiClientProblem -c DeliveryApiClient -i "**/{path}" --ebc --ll Trace
Thanks for raising this @fzzle
This looks to be a duplicate of https://github.com/microsoft/kiota/issues/4178. @andreaTP Any chance you can confirm?
Looks very similar, @fzzle any chance you can test using Kiota from my branch ( https://github.com/andreaTP/kiota/tree/initial-fixes-simple-oneOf-py ) with the fixes or from this (non-official)"pre-release" which includes the fixes: https://github.com/andreaTP/kiota-prerelease/releases/tag/v0.0.0-pre%2BandreaTP.initial-fixes-simple-oneOf-py.b46369a
Hi @andrueastman and @andreaTP,
Thanks for the swift reply.
I've tested your branch and I can see that it solves the problem.
The model now looks like this.
// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace Namespace.Models {
/// <summary>
/// Composed type wrapper for classes <see cref="ElementModel1"/>, <see cref="ElementModel2"/>
/// </summary>
public class ElementModelBase : IComposedTypeWrapper, IParsable
{
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel1? ElementModel1 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel1 ElementModel1 { get; set; }
#endif
/// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public Namespace.Models.ElementModel2? ElementModel2 { get; set; }
#nullable restore
#else
public Namespace.Models.ElementModel2 ElementModel2 { get; set; }
#endif
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="ElementModelBase"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static ElementModelBase CreateFromDiscriminatorValue(IParseNode parseNode)
{
_ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
var mappingValue = parseNode.GetChildNode("contentType")?.GetStringValue();
var result = new ElementModelBase();
if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModel1 = new Namespace.Models.ElementModel1();
}
else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
{
result.ElementModel2 = new Namespace.Models.ElementModel2();
}
return result;
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary<string, Action<IParseNode>></returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
if(ElementModel1 != null)
{
return ElementModel1.GetFieldDeserializers();
}
else if(ElementModel2 != null)
{
return ElementModel2.GetFieldDeserializers();
}
return new Dictionary<string, Action<IParseNode>>();
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
_ = writer ?? throw new ArgumentNullException(nameof(writer));
if(ElementModel1 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModel1);
}
else if(ElementModel2 != null)
{
writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModel2);
}
}
}
}
@fzzle @andreaTP can you try with the latest preview from yesterday and confirm whether you observe the problem? We've made significant improvements to the handling of allof edge scenarios with #4668 and #4381
Checked and the problem is still present.
I'm looking forward to this being fixed. We're still using the non-official version from @andreaTP
cc. @baywet just to be sure that this is under your radar
This also happens to be resolved with https://github.com/microsoft/kiota/pull/5657