anyOf-referenced model generated with no properties when the composed reference is traversed before a direct $ref
- Dominant language
- C#
- Stars
- 3.8k
- Forks
- 333
- Avg merge
- 16h 29m
- Merged PRs (30d)
- 116
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
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> GetFieldDeserializers()
{
return new Dictionary>
{
};
}
```
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
```
```
### 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#.
Contributor guide
Research direction
Start with the supplied openapi.json and run the shown Kiota command with the get/anyOf operation first, then compare out/Models/Thing.cs with the control ordering. Trace how the Thing schema is materialized through anyOf and reused by the direct $ref. Done means Thing retains Id, Name, and Count plus matching deserialization and serialization behavior regardless of traversal order.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100