Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Models with discriminator get superfluous fields #4178

Closed
isinyaaa opened this issue Feb 15, 2024 · 11 comments · Fixed by #5657
Closed

Models with discriminator get superfluous fields #4178

isinyaaa opened this issue Feb 15, 2024 · 11 comments · Fixed by #5657
Assignees
Labels
generator Issues or improvements relater to generation capabilities. type:bug A broken experience WIP
Milestone

Comments

@isinyaaa
Copy link

I have a simple model that can of two types: A or B, like what's shown in the spec below.

openapi: 3.0.3
info:
  title: Example API
  version: 1.0.0
servers:
  - url: "https://localhost:8080"
paths:
  "/api/all":
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AorB"
        required: true
      responses:
        "200":
          $ref: "#/components/responses/AorBResponse"
components:
  schemas:
    A:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          default: "a"
    B:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          default: "b"
    AorB:
      oneOf:
        - $ref: "#/components/schemas/A"
        - $ref: "#/components/schemas/B"
      discriminator:
        propertyName: type
        mapping:
          a: "#/components/schemas/A"
          b: "#/components/schemas/B"
  responses:
    AorBResponse:
      description: mandatory
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/AorB"

When I try to generate code (tested with both Python and Go), I get some superfluous values and unnecessary checks. e.g. in Python:

from __future__ import annotations
from dataclasses import dataclass, field
from kiota_abstractions.serialization import Parsable, ParseNode, SerializationWriter
from kiota_abstractions.serialization.composed_type_wrapper import ComposedTypeWrapper
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union

if TYPE_CHECKING:
    from .a import A
    from .b import B

@dataclass
class AorB(ComposedTypeWrapper, Parsable):
    """
    Composed type wrapper for classes A, B
    """
    @staticmethod
    def create_from_discriminator_value(parse_node: Optional[ParseNode] = None) -> AorB:
        """
        Creates a new instance of the appropriate class based on discriminator value
        param parse_node: The parse node to use to read the discriminator value and create the object
        Returns: AorB
        """
        if not parse_node:
            raise TypeError("parse_node cannot be null.")
        try:
            mapping_value = parse_node.get_child_node("type").get_str_value()
        except AttributeError:
            mapping_value = None
        result = AorB()
        if mapping_value and mapping_value.casefold() == "a".casefold():
            from .a import A

            result.a = A()
        elif mapping_value and mapping_value.casefold() == "a".casefold():
            from .a import A

            result.aor_b_a = A()
        elif mapping_value and mapping_value.casefold() == "a".casefold():
            from .a import A

            result.aor_b_a0 = A()
        elif mapping_value and mapping_value.casefold() == "b".casefold():
            from .b import B

            result.aor_b_b = B()
        elif mapping_value and mapping_value.casefold() == "b".casefold():
            from .b import B

            result.aor_b_b0 = B()
        elif mapping_value and mapping_value.casefold() == "b".casefold():
            from .b import B

            result.b = B()
        return result
    
    def get_field_deserializers(self,) -> Dict[str, Callable[[ParseNode], None]]:
        """
        The deserialization information for the current model
        Returns: Dict[str, Callable[[ParseNode], None]]
        """
        from .a import A
        from .b import B

        if self.a:
            return self.a.get_field_deserializers()
        if self.aor_b_a:
            return self.aor_b_a.get_field_deserializers()
        if self.aor_b_a0:
            return self.aor_b_a0.get_field_deserializers()
        if self.aor_b_b:
            return self.aor_b_b.get_field_deserializers()
        if self.aor_b_b0:
            return self.aor_b_b0.get_field_deserializers()
        if self.b:
            return self.b.get_field_deserializers()
        return {}
    
    def serialize(self,writer: SerializationWriter) -> None:
        """
        Serializes information the current object
        param writer: Serialization writer to use to serialize this model
        Returns: None
        """
        if not writer:
            raise TypeError("writer cannot be null.")
        if self.a:
            writer.write_object_value(None, self.a)
        elif self.aor_b_a:
            writer.write_object_value(None, self.aor_b_a)
        elif self.aor_b_a0:
            writer.write_object_value(None, self.aor_b_a0)
        elif self.aor_b_b:
            writer.write_object_value(None, self.aor_b_b)
        elif self.aor_b_b0:
            writer.write_object_value(None, self.aor_b_b0)
        elif self.b:
            writer.write_object_value(None, self.b)

Ideally, the A or B type should simply correspond to something like

class AorB(BaseModel):
    a: A | None
    b: B | None

that is, without any superfluous fields.

@andrueastman
Copy link
Member

Thanks for raising this @isinyaaa

The get_field_deserializers, serialize and create_from_discriminator_value are methods generated in all models generated by Kiota so that they may used by the relevant serializers to infer the type/property information needed a runtime.

https://learn.microsoft.com/en-us/openapi/kiota/models#field-deserializers

Any chance you can confirm if there's an issue the existing members cause in your scenario?

@andreaTP
Copy link
Contributor

Thanks for reverting back @andrueastman !

Any chance you can confirm if there's an issue the existing members cause in your scenario?

I cannot find any and the reproducer shared by @isinyaaa shows that the duplication of the fields is happening even on a minimal example.

@isinyaaa
Copy link
Author

Thanks for raising this @isinyaaa

The get_field_deserializers, serialize and create_from_discriminator_value are methods generated in all models generated by Kiota so that they may used by the relevant serializers to infer the type/property information needed a runtime.

https://learn.microsoft.com/en-us/openapi/kiota/models#field-deserializers

Any chance you can confirm if there's an issue the existing members cause in your scenario?

At least on the Python case, I can't find any problems, but I'm not sure on the patterns used for other languages that might e.g. use those unnecessary fields in some situation and fail to generate a valid object upon serialization.

@andrueastman
Copy link
Member

Sorry. I suspect that I may have not got the gist of the issue correctly.

Just to confirm, the issue here is not with the method members i.e. get_field_deserializers, serialize and create_from_discriminator_value but that the generated model has self.a, self.aor_b_a, self.aor_b_a0, self.aor_b_b, self.aor_b_b0 and self.b when we should ideally only expect self.a and self.b.
Yes?

@andreaTP
Copy link
Contributor

That's correct.

@baywet
Copy link
Member

baywet commented Feb 22, 2024

Just chiming in here

I believe the expected result given this input OpenAPI description should be

from __future__ import annotations
from dataclasses import dataclass, field
from kiota_abstractions.serialization import Parsable, ParseNode, SerializationWriter
from kiota_abstractions.serialization.composed_type_wrapper import ComposedTypeWrapper
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union

if TYPE_CHECKING:
    from .a import A
    from .b import B

@dataclass
class AorB(ComposedTypeWrapper, Parsable):
    """
    Composed type wrapper for classes A, B
    """
    @staticmethod
    def create_from_discriminator_value(parse_node: Optional[ParseNode] = None) -> AorB:
        """
        Creates a new instance of the appropriate class based on discriminator value
        param parse_node: The parse node to use to read the discriminator value and create the object
        Returns: AorB
        """
        if not parse_node:
            raise TypeError("parse_node cannot be null.")
        try:
            mapping_value = parse_node.get_child_node("type").get_str_value()
        except AttributeError:
            mapping_value = None
        result = AorB()
        if mapping_value and mapping_value.casefold() == "a".casefold():
            from .a import A

            result.a = A()
-       elif mapping_value and mapping_value.casefold() == "a".casefold():
-           from .a import A
-
-           result.aor_b_a = A()
-       elif mapping_value and mapping_value.casefold() == "a".casefold():
-           from .a import A
-
-           result.aor_b_a0 = A()
-       elif mapping_value and mapping_value.casefold() == "b".casefold():
-           from .b import B
-
-           result.aor_b_b = B()
-       elif mapping_value and mapping_value.casefold() == "b".casefold():
-           from .b import B
-
-           result.aor_b_b0 = B()
        elif mapping_value and mapping_value.casefold() == "b".casefold():
            from .b import B

            result.b = B()
        return result
    
    def get_field_deserializers(self,) -> Dict[str, Callable[[ParseNode], None]]:
        """
        The deserialization information for the current model
        Returns: Dict[str, Callable[[ParseNode], None]]
        """
        from .a import A
        from .b import B

        if self.a:
            return self.a.get_field_deserializers()
-       if self.aor_b_a:
-           return self.aor_b_a.get_field_deserializers()
-       if self.aor_b_a0:
-           return self.aor_b_a0.get_field_deserializers()
-       if self.aor_b_b:
-           return self.aor_b_b.get_field_deserializers()
-       if self.aor_b_b0:
-           return self.aor_b_b0.get_field_deserializers()
        if self.b:
            return self.b.get_field_deserializers()
        return {}
    
    def serialize(self,writer: SerializationWriter) -> None:
        """
        Serializes information the current object
        param writer: Serialization writer to use to serialize this model
        Returns: None
        """
        if not writer:
            raise TypeError("writer cannot be null.")
        if self.a:
            writer.write_object_value(None, self.a)
-       elif self.aor_b_a:
-           writer.write_object_value(None, self.aor_b_a)
-       elif self.aor_b_a0:
-           writer.write_object_value(None, self.aor_b_a0)
-       elif self.aor_b_b:
-           writer.write_object_value(None, self.aor_b_b)
-       elif self.aor_b_b0:
-           writer.write_object_value(None, self.aor_b_b0)
        elif self.b:
            writer.write_object_value(None, self.b)

One first step to understand better where this issue is coming from would be to compare the result across languages to see whether we get this duplication consistently, or simply for Python.
@isinyaaa Would you mind running the generation across languages (except TypeScript/Ruby/Swift) and reporting the behaviour here please?

@baywet baywet added this to the Backlog milestone Feb 22, 2024
@baywet baywet added the type:bug A broken experience label Feb 22, 2024
@isinyaaa
Copy link
Author

One first step to understand better where this issue is coming from would be to compare the result across languages to see whether we get this duplication consistently, or simply for Python. @isinyaaa Would you mind running the generation across languages (except TypeScript/Ruby/Swift) and reporting the behaviour here please?

Here you go:

CLI/C# (`Models/AorB.cs`)
// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace ApiSdk.Models {
    /// <summary>
    /// Composed type wrapper for classes A, B
    /// </summary>
    public class AorB : IParsable {
        /// <summary>Composed type representation for type A</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.A? A { get; set; }
#nullable restore
#else
        public ApiSdk.Models.A A { get; set; }
#endif
        /// <summary>Composed type representation for type A</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.A? AorBA { get; set; }
#nullable restore
#else
        public ApiSdk.Models.A AorBA { get; set; }
#endif
        /// <summary>Composed type representation for type A</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.A? AorBA0 { get; set; }
#nullable restore
#else
        public ApiSdk.Models.A AorBA0 { get; set; }
#endif
        /// <summary>Composed type representation for type B</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.B? AorBB { get; set; }
#nullable restore
#else
        public ApiSdk.Models.B AorBB { get; set; }
#endif
        /// <summary>Composed type representation for type B</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.B? AorBB0 { get; set; }
#nullable restore
#else
        public ApiSdk.Models.B AorBB0 { get; set; }
#endif
        /// <summary>Composed type representation for type B</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public ApiSdk.Models.B? B { get; set; }
#nullable restore
#else
        public ApiSdk.Models.B B { get; set; }
#endif
        /// <summary>
        /// Creates a new instance of the appropriate class based on discriminator value
        /// </summary>
        /// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
        public static AorB CreateFromDiscriminatorValue(IParseNode parseNode) {
            _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
            var mappingValue = parseNode.GetChildNode("type")?.GetStringValue();
            var result = new AorB();
            if("a".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.A = new ApiSdk.Models.A();
            }
            else if("a".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.AorBA = new ApiSdk.Models.A();
            }
            else if("a".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.AorBA0 = new ApiSdk.Models.A();
            }
            else if("b".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.AorBB = new ApiSdk.Models.B();
            }
            else if("b".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.AorBB0 = new ApiSdk.Models.B();
            }
            else if("b".Equals(mappingValue, StringComparison.OrdinalIgnoreCase)) {
                result.B = new ApiSdk.Models.B();
            }
            return result;
        }
        /// <summary>
        /// The deserialization information for the current model
        /// </summary>
        public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers() {
            if(A != null) {
                return A.GetFieldDeserializers();
            }
            else if(AorBA != null) {
                return AorBA.GetFieldDeserializers();
            }
            else if(AorBA0 != null) {
                return AorBA0.GetFieldDeserializers();
            }
            else if(AorBB != null) {
                return AorBB.GetFieldDeserializers();
            }
            else if(AorBB0 != null) {
                return AorBB0.GetFieldDeserializers();
            }
            else if(B != null) {
                return B.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(A != null) {
                writer.WriteObjectValue<ApiSdk.Models.A>(null, A);
            }
            else if(AorBA != null) {
                writer.WriteObjectValue<ApiSdk.Models.A>(null, AorBA);
            }
            else if(AorBA0 != null) {
                writer.WriteObjectValue<ApiSdk.Models.A>(null, AorBA0);
            }
            else if(AorBB != null) {
                writer.WriteObjectValue<ApiSdk.Models.B>(null, AorBB);
            }
            else if(AorBB0 != null) {
                writer.WriteObjectValue<ApiSdk.Models.B>(null, AorBB0);
            }
            else if(B != null) {
                writer.WriteObjectValue<ApiSdk.Models.B>(null, B);
            }
        }
    }
}
Go (`models/aor_b.go`)
package models

import (
    ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6 "strings"
    i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization"
)

// AorB composed type wrapper for classes A, B
type AorB struct {
    // Composed type representation for type A
    a Aable
    // Composed type representation for type A
    aorBA Aable
    // Composed type representation for type A
    aorBA0 Aable
    // Composed type representation for type B
    aorBB Bable
    // Composed type representation for type B
    aorBB0 Bable
    // Composed type representation for type B
    b Bable
}
// NewAorB instantiates a new AorB and sets the default values.
func NewAorB()(*AorB) {
    m := &AorB{
    }
    return m
}
// CreateAorBFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value
func CreateAorBFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) {
    result := NewAorB()
    if parseNode != nil {
        mappingValueNode, err := parseNode.GetChildNode("type")
        if err != nil {
            return nil, err
        }
        if mappingValueNode != nil {
            mappingValue, err := mappingValueNode.GetStringValue()
            if err != nil {
                return nil, err
            }
            if mappingValue != nil {
                if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "a") {
                    result.SetA(NewA())
                } else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "a") {
                    result.SetAorBA(NewA())
                } else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "a") {
                    result.SetAorBA0(NewA())
                } else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "b") {
                    result.SetAorBB(NewB())
                } else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "b") {
                    result.SetAorBB0(NewB())
                } else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "b") {
                    result.SetB(NewB())
                }
            }
        }
    }
    return result, nil
}
// GetA gets the A property value. Composed type representation for type A
func (m *AorB) GetA()(Aable) {
    return m.a
}
// GetAorBA gets the A property value. Composed type representation for type A
func (m *AorB) GetAorBA()(Aable) {
    return m.aorBA
}
// GetAorBA0 gets the A property value. Composed type representation for type A
func (m *AorB) GetAorBA0()(Aable) {
    return m.aorBA0
}
// GetAorBB gets the B property value. Composed type representation for type B
func (m *AorB) GetAorBB()(Bable) {
    return m.aorBB
}
// GetAorBB0 gets the B property value. Composed type representation for type B
func (m *AorB) GetAorBB0()(Bable) {
    return m.aorBB0
}
// GetB gets the B property value. Composed type representation for type B
func (m *AorB) GetB()(Bable) {
    return m.b
}
// GetFieldDeserializers the deserialization information for the current model
func (m *AorB) GetFieldDeserializers()(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error)) {
    return make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error))
}
// GetIsComposedType determines if the current object is a wrapper around a composed type
func (m *AorB) GetIsComposedType()(bool) {
    return true
}
// Serialize serializes information the current object
func (m *AorB) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter)(error) {
    if m.GetA() != nil {
        err := writer.WriteObjectValue("", m.GetA())
        if err != nil {
            return err
        }
    } else if m.GetAorBA() != nil {
        err := writer.WriteObjectValue("", m.GetAorBA())
        if err != nil {
            return err
        }
    } else if m.GetAorBA0() != nil {
        err := writer.WriteObjectValue("", m.GetAorBA0())
        if err != nil {
            return err
        }
    } else if m.GetAorBB() != nil {
        err := writer.WriteObjectValue("", m.GetAorBB())
        if err != nil {
            return err
        }
    } else if m.GetAorBB0() != nil {
        err := writer.WriteObjectValue("", m.GetAorBB0())
        if err != nil {
            return err
        }
    } else if m.GetB() != nil {
        err := writer.WriteObjectValue("", m.GetB())
        if err != nil {
            return err
        }
    }
    return nil
}
// SetA sets the A property value. Composed type representation for type A
func (m *AorB) SetA(value Aable)() {
    m.a = value
}
// SetAorBA sets the A property value. Composed type representation for type A
func (m *AorB) SetAorBA(value Aable)() {
    m.aorBA = value
}
// SetAorBA0 sets the A property value. Composed type representation for type A
func (m *AorB) SetAorBA0(value Aable)() {
    m.aorBA0 = value
}
// SetAorBB sets the B property value. Composed type representation for type B
func (m *AorB) SetAorBB(value Bable)() {
    m.aorBB = value
}
// SetAorBB0 sets the B property value. Composed type representation for type B
func (m *AorB) SetAorBB0(value Bable)() {
    m.aorBB0 = value
}
// SetB sets the B property value. Composed type representation for type B
func (m *AorB) SetB(value Bable)() {
    m.b = value
}
// AorBable 
type AorBable interface {
    i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable
    GetA()(Aable)
    GetAorBA()(Aable)
    GetAorBA0()(Aable)
    GetAorBB()(Bable)
    GetAorBB0()(Bable)
    GetB()(Bable)
    SetA(value Aable)()
    SetAorBA(value Aable)()
    SetAorBA0(value Aable)()
    SetAorBB(value Bable)()
    SetAorBB0(value Bable)()
    SetB(value Bable)()
}
Java (`apisdk/models/AorB.java`)
package apisdk.models;

import com.microsoft.kiota.serialization.ComposedTypeWrapper;
import com.microsoft.kiota.serialization.Parsable;
import com.microsoft.kiota.serialization.ParseNode;
import com.microsoft.kiota.serialization.SerializationWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
 * Composed type wrapper for classes A, B
 */
@jakarta.annotation.Generated("com.microsoft.kiota")
public class AorB implements ComposedTypeWrapper, Parsable {
    /**
     * Composed type representation for type A
     */
    private A a;
    /**
     * Composed type representation for type A
     */
    private A aorBA;
    /**
     * Composed type representation for type A
     */
    private A aorBA0;
    /**
     * Composed type representation for type B
     */
    private B aorBB;
    /**
     * Composed type representation for type B
     */
    private B aorBB0;
    /**
     * Composed type representation for type B
     */
    private B b;
    /**
     * Creates a new instance of the appropriate class based on discriminator value
     * @param parseNode The parse node to use to read the discriminator value and create the object
     * @return a AorB
     */
    @jakarta.annotation.Nonnull
    public static AorB createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) {
        Objects.requireNonNull(parseNode);
        final AorB result = new AorB();
        final ParseNode mappingValueNode = parseNode.getChildNode("type");
        if (mappingValueNode != null) {
            final String mappingValue = mappingValueNode.getStringValue();
            if ("a".equalsIgnoreCase(mappingValue)) {
                result.setA(new A());
            } else if ("a".equalsIgnoreCase(mappingValue)) {
                result.setAorBA(new A());
            } else if ("a".equalsIgnoreCase(mappingValue)) {
                result.setAorBA0(new A());
            } else if ("b".equalsIgnoreCase(mappingValue)) {
                result.setAorBB(new B());
            } else if ("b".equalsIgnoreCase(mappingValue)) {
                result.setAorBB0(new B());
            } else if ("b".equalsIgnoreCase(mappingValue)) {
                result.setB(new B());
            }
        }
        return result;
    }
    /**
     * Gets the A property value. Composed type representation for type A
     * @return a A
     */
    @jakarta.annotation.Nullable
    public A getA() {
        return this.a;
    }
    /**
     * Gets the A property value. Composed type representation for type A
     * @return a A
     */
    @jakarta.annotation.Nullable
    public A getAorBA() {
        return this.aorBA;
    }
    /**
     * Gets the A property value. Composed type representation for type A
     * @return a A
     */
    @jakarta.annotation.Nullable
    public A getAorBA0() {
        return this.aorBA0;
    }
    /**
     * Gets the B property value. Composed type representation for type B
     * @return a B
     */
    @jakarta.annotation.Nullable
    public B getAorBB() {
        return this.aorBB;
    }
    /**
     * Gets the B property value. Composed type representation for type B
     * @return a B
     */
    @jakarta.annotation.Nullable
    public B getAorBB0() {
        return this.aorBB0;
    }
    /**
     * Gets the B property value. Composed type representation for type B
     * @return a B
     */
    @jakarta.annotation.Nullable
    public B getB() {
        return this.b;
    }
    /**
     * The deserialization information for the current model
     * @return a Map<String, java.util.function.Consumer<ParseNode>>
     */
    @jakarta.annotation.Nonnull
    public Map<String, java.util.function.Consumer<ParseNode>> getFieldDeserializers() {
        if (this.getA() != null) {
            return this.getA().getFieldDeserializers();
        } else if (this.getAorBA() != null) {
            return this.getAorBA().getFieldDeserializers();
        } else if (this.getAorBA0() != null) {
            return this.getAorBA0().getFieldDeserializers();
        } else if (this.getAorBB() != null) {
            return this.getAorBB().getFieldDeserializers();
        } else if (this.getAorBB0() != null) {
            return this.getAorBB0().getFieldDeserializers();
        } else if (this.getB() != null) {
            return this.getB().getFieldDeserializers();
        }
        return new HashMap<String, java.util.function.Consumer<ParseNode>>();
    }
    /**
     * Serializes information the current object
     * @param writer Serialization writer to use to serialize this model
     */
    public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writer) {
        Objects.requireNonNull(writer);
        if (this.getA() != null) {
            writer.writeObjectValue(null, this.getA());
        } else if (this.getAorBA() != null) {
            writer.writeObjectValue(null, this.getAorBA());
        } else if (this.getAorBA0() != null) {
            writer.writeObjectValue(null, this.getAorBA0());
        } else if (this.getAorBB() != null) {
            writer.writeObjectValue(null, this.getAorBB());
        } else if (this.getAorBB0() != null) {
            writer.writeObjectValue(null, this.getAorBB0());
        } else if (this.getB() != null) {
            writer.writeObjectValue(null, this.getB());
        }
    }
    /**
     * Sets the A property value. Composed type representation for type A
     * @param value Value to set for the A property.
     */
    public void setA(@jakarta.annotation.Nullable final A value) {
        this.a = value;
    }
    /**
     * Sets the A property value. Composed type representation for type A
     * @param value Value to set for the A property.
     */
    public void setAorBA(@jakarta.annotation.Nullable final A value) {
        this.aorBA = value;
    }
    /**
     * Sets the A property value. Composed type representation for type A
     * @param value Value to set for the A property.
     */
    public void setAorBA0(@jakarta.annotation.Nullable final A value) {
        this.aorBA0 = value;
    }
    /**
     * Sets the B property value. Composed type representation for type B
     * @param value Value to set for the B property.
     */
    public void setAorBB(@jakarta.annotation.Nullable final B value) {
        this.aorBB = value;
    }
    /**
     * Sets the B property value. Composed type representation for type B
     * @param value Value to set for the B property.
     */
    public void setAorBB0(@jakarta.annotation.Nullable final B value) {
        this.aorBB0 = value;
    }
    /**
     * Sets the B property value. Composed type representation for type B
     * @param value Value to set for the B property.
     */
    public void setB(@jakarta.annotation.Nullable final B value) {
        this.b = value;
    }
}
Bonus (?) PHP (`Api/All/AorB.php`)
<?php

namespace ApiSdk\Api\All;

use ApiSdk\Models\A;
use ApiSdk\Models\B;
use Microsoft\Kiota\Abstractions\Serialization\Parsable;
use Microsoft\Kiota\Abstractions\Serialization\ParseNode;
use Microsoft\Kiota\Abstractions\Serialization\SerializationWriter;

/**
 * Composed type wrapper for classes A, B
*/
class AorB implements Parsable 
{
    /**
     * @var A|null $a Composed type representation for type A
    */
    private ?A $a = null;
    
    /**
     * @var A|null $aorBA Composed type representation for type A
    */
    private ?A $aorBA = null;
    
    /**
     * @var A|null $aorBA0 Composed type representation for type A
    */
    private ?A $aorBA0 = null;
    
    /**
     * @var B|null $aorBB Composed type representation for type B
    */
    private ?B $aorBB = null;
    
    /**
     * @var B|null $aorBB0 Composed type representation for type B
    */
    private ?B $aorBB0 = null;
    
    /**
     * @var B|null $b Composed type representation for type B
    */
    private ?B $b = null;
    
    /**
     * Creates a new instance of the appropriate class based on discriminator value
     * @param ParseNode $parseNode The parse node to use to read the discriminator value and create the object
     * @return AorB
    */
    public static function createFromDiscriminatorValue(ParseNode $parseNode): AorB {
        $result = new AorB();
        $mappingValueNode = $parseNode->getChildNode("type");
        if ($mappingValueNode !== null) {
            $mappingValue = $mappingValueNode->getStringValue();
            if ('a' === $mappingValue) {
                $result->setA(new A());
            } else if ('a' === $mappingValue) {
                $result->setAorBA(new A());
            } else if ('a' === $mappingValue) {
                $result->setAorBA0(new A());
            } else if ('b' === $mappingValue) {
                $result->setAorBB(new B());
            } else if ('b' === $mappingValue) {
                $result->setAorBB0(new B());
            } else if ('b' === $mappingValue) {
                $result->setB(new B());
            }
        }
        return $result;
    }

    /**
     * Gets the A property value. Composed type representation for type A
     * @return A|null
    */
    public function getA(): ?A {
        return $this->a;
    }

    /**
     * Gets the A property value. Composed type representation for type A
     * @return A|null
    */
    public function getAorBA(): ?A {
        return $this->aorBA;
    }

    /**
     * Gets the A property value. Composed type representation for type A
     * @return A|null
    */
    public function getAorBA0(): ?A {
        return $this->aorBA0;
    }

    /**
     * Gets the B property value. Composed type representation for type B
     * @return B|null
    */
    public function getAorBB(): ?B {
        return $this->aorBB;
    }

    /**
     * Gets the B property value. Composed type representation for type B
     * @return B|null
    */
    public function getAorBB0(): ?B {
        return $this->aorBB0;
    }

    /**
     * Gets the B property value. Composed type representation for type B
     * @return B|null
    */
    public function getB(): ?B {
        return $this->b;
    }

    /**
     * The deserialization information for the current model
     * @return array<string, callable(ParseNode): void>
    */
    public function getFieldDeserializers(): array {
        if ($this->getA() !== null) {
            return $this->getA()->getFieldDeserializers();
        } else if ($this->getAorBA() !== null) {
            return $this->getAorBA()->getFieldDeserializers();
        } else if ($this->getAorBA0() !== null) {
            return $this->getAorBA0()->getFieldDeserializers();
        } else if ($this->getAorBB() !== null) {
            return $this->getAorBB()->getFieldDeserializers();
        } else if ($this->getAorBB0() !== null) {
            return $this->getAorBB0()->getFieldDeserializers();
        } else if ($this->getB() !== null) {
            return $this->getB()->getFieldDeserializers();
        }
        return [];
    }

    /**
     * Serializes information the current object
     * @param SerializationWriter $writer Serialization writer to use to serialize this model
    */
    public function serialize(SerializationWriter $writer): void {
        if ($this->getA() !== null) {
            $writer->writeObjectValue(null, $this->getA());
        } else if ($this->getAorBA() !== null) {
            $writer->writeObjectValue(null, $this->getAorBA());
        } else if ($this->getAorBA0() !== null) {
            $writer->writeObjectValue(null, $this->getAorBA0());
        } else if ($this->getAorBB() !== null) {
            $writer->writeObjectValue(null, $this->getAorBB());
        } else if ($this->getAorBB0() !== null) {
            $writer->writeObjectValue(null, $this->getAorBB0());
        } else if ($this->getB() !== null) {
            $writer->writeObjectValue(null, $this->getB());
        }
    }

    /**
     * Sets the A property value. Composed type representation for type A
     * @param A|null $value Value to set for the A property.
    */
    public function setA(?A $value): void {
        $this->a = $value;
    }

    /**
     * Sets the A property value. Composed type representation for type A
     * @param A|null $value Value to set for the A property.
    */
    public function setAorBA(?A $value): void {
        $this->aorBA = $value;
    }

    /**
     * Sets the A property value. Composed type representation for type A
     * @param A|null $value Value to set for the A property.
    */
    public function setAorBA0(?A $value): void {
        $this->aorBA0 = $value;
    }

    /**
     * Sets the B property value. Composed type representation for type B
     * @param B|null $value Value to set for the B property.
    */
    public function setAorBB(?B $value): void {
        $this->aorBB = $value;
    }

    /**
     * Sets the B property value. Composed type representation for type B
     * @param B|null $value Value to set for the B property.
    */
    public function setAorBB0(?B $value): void {
        $this->aorBB0 = $value;
    }

    /**
     * Sets the B property value. Composed type representation for type B
     * @param B|null $value Value to set for the B property.
    */
    public function setB(?B $value): void {
        $this->b = $value;
    }

}

@baywet
Copy link
Member

baywet commented Feb 22, 2024

Thank you for the additional detailed information.
Next step would be to understand better where the problem is coming from, would you mind putting together a draft PR with a test case similar to this one but with your repro instead.

Then debugging that unit test (we need to make sure it calls into the refiners as well), it'd be nice to set a breakpoint in this method to understand if when we get here we have two or more entries.
If more than two, the issue is in the main builder (I'll provide more information once we have the findings)
If two, the issue is most likely in this method.

@andreaTP
Copy link
Contributor

andreaTP commented Apr 3, 2024

I'm making some progress over this one.

Key takeaways:

  • the issue happens when an object using a oneOf + discriminator appears in the requesetBody
  • a new pair of spurious fields is produced at each occurrence of the object(in any position) in the description

@baywet
Copy link
Member

baywet commented May 17, 2024

@isinyaaa @andreaTP can you please 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

@baywet baywet added generator Issues or improvements relater to generation capabilities. status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close and removed needs more information Needs: Attention 👋 labels May 17, 2024
@andreaTP
Copy link
Contributor

Checked and the problem is still present.

@baywet baywet removed the status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close label May 23, 2024
@timayabi2020 timayabi2020 moved this from Todo 📃 to Proposed 💡 in Kiota Sep 23, 2024
@calebkiage calebkiage self-assigned this Oct 8, 2024
@andrueastman andrueastman moved this from Proposed 💡 to In Progress 🚧 in Kiota Oct 22, 2024
@andrueastman andrueastman modified the milestones: Backlog, Kiota v1.20 Oct 22, 2024
@github-project-automation github-project-automation bot moved this from In Progress 🚧 to Done ✔️ in Kiota Oct 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator Issues or improvements relater to generation capabilities. type:bug A broken experience WIP
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

6 participants