microsoft/kiota

Incorrect deserialization logic for nested array of discriminated union types in TypeScript

Open

#6,615 opened on Jun 3, 2025

View on GitHub
 (4 comments) (0 reactions) (0 assignees)C# (326 forks)auto 404
Needs: Attention :wave:TypeScripthelp wantedpriority:p3type:bug

Repository metrics

Stars
 (3,783 stars)
PR merge metrics
 (PR metrics pending)

Description

What are you generating using Kiota, clients or plugins?

API Client/SDK

In what context or format are you using Kiota?

Nuget tool

Client library/SDK language

TypeScript

Describe the bug

The TypeScript generator creates incorrect deserialization logic for an array of a polymorphic type when that array is a property of another model. Instead of using the discriminator mapping for each element in the collection, the generated code attempts to deserialize the entire collection into one of the possible types, and then tries the next type if the first attempt fails. This results in incorrectly deserialized objects within the array.

This issue does not occur when an endpoint returns the array of the polymorphic type directly. In that case, the discriminator logic is applied correctly.

I also generated the corresponding C# client for the OpenAPI yaml which deserialized the nested array correctly, so this appears to be typescript related.

As an example I have the following model (csharp, for better readability, api definition is attached via the link):

public class WeatherForecastSummary
{
    public List<WeatherForecast> Forecasts { get; set; } = [];
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = "weatherType")]
[JsonDerivedType(typeof(RainyDayForecast), "Rain")]
[JsonDerivedType(typeof(SunnyDayForecast), "Sun")]
public abstract class WeatherForecast
{
    public DateOnly Date { get; set; }
}

public class RainyDayForecast : WeatherForecast
{
    public int RainfallAmount { get; set; }
}

public class SunnyDayForecast : WeatherForecast
{
    public int TemperatureC { get; set; }
}

This is the incorrect code generated for the WeatherForecastSummary model's deserialization:

export function deserializeIntoWeatherForecastSummary(weatherForecastSummary: Partial<WeatherForecastSummary> | undefined = {}): Record<string, (node: ParseNode) => void> {
    return {
        "forecasts": n => {
            weatherForecastSummary.forecasts =
                n.getCollectionOfObjectValues<WeatherForecastRainyDayForecast>(createWeatherForecastRainyDayForecastFromDiscriminatorValue)
                ?? n.getCollectionOfObjectValues<WeatherForecastSunnyDayForecast>(createWeatherForecastSunnyDayForecastFromDiscriminatorValue);
        },
    }
}

This code tries to deserialize the entire forecasts array as WeatherForecastRainyDayForecast[] and if that doesn't work, it tries WeatherForecastSunnyDayForecast[].

For comparison, when the response is a direct array of WeatherForecast, Kiota correctly generates a factory method that uses the discriminator:

export function createWeatherForecastFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record<string, (node: ParseNode) => void>) {
    const mappingValueNode = parseNode?.getChildNode("weatherType");
    if (mappingValueNode) {
        const mappingValue = mappingValueNode.getStringValue();
        if (mappingValue) {
            switch (mappingValue) {
                case "Rain":
                    return deserializeIntoWeatherForecastRainyDayForecast;
                case "Sun":
                    return deserializeIntoWeatherForecastSunnyDayForecast;
            }
        }
    }
    return deserializeIntoWeatherForecast;
}

export const SummaryRequestBuilderRequestsMetadata: RequestsMetadata = {
    get: {
        uriTemplate: SummaryRequestBuilderUriTemplate,
        responseBodyContentType: "application/json",
        adapterMethodName: "send",
        responseBodyFactory:  createWeatherForecastSummaryFromDiscriminatorValue,
    },
};

Expected behavior

The deserialization logic for the forecasts property within deserializeIntoWeatherForecastSummary should leverage the createWeatherForecastFromDiscriminatorValue factory for each item in the collection, not for the collection as a whole. The generated code should look more like this:

// Conceptual correct implementation
export function deserializeIntoWeatherForecastSummary(weatherForecastSummary: Partial<WeatherForecastSummary> | undefined = {}): Record<string, (node: ParseNode) => void> {
    return {
        "forecasts": n => {
            weatherForecastSummary.forecasts = n.getCollectionOfObjectValues(createWeatherForecastFromDiscriminatorValue);
        },
    }
}

How to reproduce

I created a minimal working example in the following repo with instructions on how to run it: https://github.com/nreinartz/kiota-ts-incorrect-derserialization

Open API description file

https://github.com/nreinartz/kiota-ts-incorrect-derserialization/blob/main/openapi.json

Kiota Version

1.26.1

Latest Kiota version known to work for scenario above?(Not required)

No response

Known Workarounds

No response

Configuration

No response

Debug output

Other information

No response

Contributor guide