microsoft / kiota

OpenAPI based HTTP Client code generator
https://aka.ms/kiota/docs
MIT License
2.9k stars 200 forks source link

Models with discriminator get superfluous fields #4178

Open isinyaaa opened 8 months ago

isinyaaa commented 8 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

That's correct.

baywet commented 7 months ago

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?

isinyaaa commented 7 months ago

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`) ```csharp // using Microsoft.Kiota.Abstractions.Serialization; using System.Collections.Generic; using System.IO; using System.Linq; using System; namespace ApiSdk.Models { /// /// Composed type wrapper for classes A, B /// public class AorB : IParsable { /// Composed type representation for type A #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 /// Composed type representation for type A #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 /// Composed type representation for type A #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 /// Composed type representation for type B #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 /// Composed type representation for type B #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 /// Composed type representation for type B #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 /// /// Creates a new instance of the appropriate class based on discriminator value /// /// The parse node to use to read the discriminator value and create the object 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; } /// /// The deserialization information for the current model /// public virtual IDictionary> 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>(); } /// /// Serializes information the current object /// /// Serialization writer to use to serialize this model public virtual void Serialize(ISerializationWriter writer) { _ = writer ?? throw new ArgumentNullException(nameof(writer)); if(A != null) { writer.WriteObjectValue(null, A); } else if(AorBA != null) { writer.WriteObjectValue(null, AorBA); } else if(AorBA0 != null) { writer.WriteObjectValue(null, AorBA0); } else if(AorBB != null) { writer.WriteObjectValue(null, AorBB); } else if(AorBB0 != null) { writer.WriteObjectValue(null, AorBB0); } else if(B != null) { writer.WriteObjectValue(null, B); } } } } ```
Go (`models/aor_b.go`) ```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`) ```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> */ @jakarta.annotation.Nonnull public Map> 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>(); } /** * 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 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 */ 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 commented 7 months ago

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 commented 6 months ago

I'm making some progress over this one.

Key takeaways:

baywet commented 4 months ago

@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

andreaTP commented 4 months ago

Checked and the problem is still present.