Skip to content

anyOf-referenced model generated with no properties when the composed reference is traversed before a direct $ref #8043

Description

@kangaaroob

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

Csharp

Describe the bug

A component schema that is referenced from an anyOf composed type is generated as an empty class (no properties, empty GetFieldDeserializers/Serialize) whenever the composed reference is traversed before any direct $ref to the same schema. Every later reference, including a direct operation response, reuses the already gutted class.

The trigger is the order in which references are traversed, not the schema itself (as far as I can tell). This is silent as the client compiles, and every response deserializes into AdditionalData with no error or warning.

Expected behavior

Thing is generated with its id, name, and count properties regardless of whether the composed type reference or the direct $ref is encountered first. Reference order in the description should not affect the shape of a generated model.

How to reproduce

Save the openapi.json file in the next section:

Generate: kiota generate -l csharp -d openapi.json -c ReproClient -n Repro -o out

out/Models/Thing.cs has no Id/Name/Count properties:

public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
    return new Dictionary<string, Action<IParseNode>>
    {
    };
}

Control: Swap the two operations so post is listed before get, changing nothing else. Thing is then generated correctly with all three properties.

Three runs of each, same machine, same kiota binary:
CONTROL (post / direct $ref listed first): 3, 3, 3 deserializer entries
BUG (get / anyOf listed first): 0, 0, 0 deserializer entries

Open API description file

{
  "openapi": "3.0.1",
  "info": { "title": "repro", "version": "1.0" },
  "paths": {
    "/things": {
      "get": {
        "operationId": "GetTree",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TreeResponse" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "CreateThing",
        "responses": {
          "201": {
            "description": "created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Thing" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "TreeResponse": {
        "type": "object",
        "title": "TreeResponse",
        "properties": {
          "things": {
            "title": "Things",
            "default": [],
            "title": "Things",
            "default": [],
            "anyOf": [
              { "type": "array", "items": { "$ref": "#/components/schemas/Thing" } },
              { "type": "array", "items": { "type": "object", "additionalProperties": true } }
            ]
          }
        }
      },
      "Thing": {
        "type": "object",
        "title": "Thing",
        "required": ["name"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "count": { "type": "integer" }
        }
      }
    }
  }
}

Kiota Version

1.32.2+eb0666c77234695508d65c00ab03827733d87557

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

None known. Also reproduces on 1.34.1.

Known Workarounds

Only spec side ones, both unsatisfying:

  1. Order the description so a direct $ref is traversed before the composed reference. Works in the minimal case, but in a real description you don't reliably control traversal order, and it silently regresses when routes are added or reordered.
  2. Remove the two alternative union, e.g. give the endpoint a distinct response schema so the affected model is never reached through an anyOf. This is the only robust fix, but it means changing the public API shape to work around a generator bug.

Reordering or renaming the victim schema doesn't help as the problem follows whichever schema is materialized through the composed type first. Redirecting the union at a different schema simply transfers the empty generation to that one.

Configuration

  • OS: Ubuntu 24.04.4 LTS
  • architecture: x86_64
  • kiota: dotnet global tool
  • .NET SDK: 8.0.403

Debug output

Click to expand log ```
</details>


### Other information

This looks like the same name based dedup mechanism described in #7791, which was fixed for the `allOf-inheritance-via-nullable-anyOf` path in 1.32.3. That fix does not cover this path as a plain object schema reached through a genuine two alternative union.

I'm fairly certain that this occurs universally on all SDK languages, not just C#.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CsharpPull requests that update .net codestatus:waiting-for-triageAn issue that is yet to be reviewed or assignedtype:bugA broken experience

    Type

    No type

    Projects

    • Status
      Needs Triage 🔍

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions