Models with discriminator get superfluous fields
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.
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?
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.
Thanks for raising this @isinyaaa
The
get_field_deserializers,serializeandcreate_from_discriminator_valueare 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.
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?
That's correct.
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?
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;
}
}
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.
I'm making some progress over this one.
Key takeaways:
- the issue happens when an object using a
oneOf+ discriminator appears in therequesetBody - a new pair of spurious fields is produced at each occurrence of the object(in any position) in the description
@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
Checked and the problem is still present.