Repository: microsoft/kiota (csharp)

Issue:
Problem generating model(s) when schema has `type: object` with `oneOf` with single ref
### 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

We came across this when we were using Kiota to generate our C# client-code against a contract which was work in progress.

In the contract we are using we have several `oneOf` hierarchies and in one case there was a hierarchy where there was only a single ref under `oneOf` but the schema had `type: object` set. If the `type: object` is set, the contract does not generate the expected models.

I have a repository with the generated clients and example schemas which reproduce the issue available at [https://github.com/vipentti/kiota-type-object-one-of-issue](https://github.com/vipentti/kiota-type-object-one-of-issue)

### Expected behavior

Kiota generates code for both schemas similarly (with or without `type: object`) because if the `type: object` is present with multiple `oneOf` refs, code is generated as expected.

```yaml
    # ... rest of the schema ...
    # Does not work (Component1) is not generated at all or mentioned in the
    # generated ExampleWithSingleOneOfWithTypeObject type.
    ExampleWithSingleOneOfWithTypeObject:
      # Removing this works
      type: object
      oneOf:
        - $ref: "#/components/schemas/Component1"
      discriminator:
        propertyName: objectType

    # Does work (Component2) is generated but ExampleWithSingleOneOfWithoutTypeObject is not
    # e.g. Component2 is used in place of ExampleWithSingleOneOfWithoutTypeObject
    ExampleWithSingleOneOfWithoutTypeObject:
      oneOf:
        - $ref: "#/components/schemas/Component2"
      discriminator:
        propertyName: objectType
    # ... rest of the schema ...
```

### How to reproduce

I have a repository with the generated clients and example schemas which reproduce the issue available at [https://github.com/vipentti/kiota-type-object-one-of-issue](https://github.com/vipentti/kiota-type-object-one-of-issue)



### Open API description file

[https://github.com/vipentti/kiota-type-object-one-of-issue/blob/main/DiscriminatorProblemSampleSimple.yaml](https://github.com/vipentti/kiota-type-object-one-of-issue/blob/main/DiscriminatorProblemSampleSimple.yaml)

### Kiota Version

1.20.0+81bdb1bf13383a749f794d47926d09733d2d7be1

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

_No response_

### Known Workarounds

Removing the `type: object` if there is only one `oneOf` seems to work, unfortunately that may not be possible if the schema is also generated by some tool and/or provided by 3rd parties.

### Configuration

- OS: Window 10
- Architecture: x64

### Debug output

<details><summary>Click to expand log</summary>
```

info: Kiota.Builder.KiotaBuilder[0]
      Cleaning output directory <repo_path>\kiota-type-object-one-of-issue\.\Generated\Simple\CSharp
dbug: Kiota.Builder.KiotaBuilder[0]
      kiota version 1.20.0
info: Kiota.Builder.KiotaBuilder[0]
      loaded description from local source
dbug: Kiota.Builder.KiotaBuilder[0]
      step 1 - reading the stream - took 00:00:00.0050763
dbug: Kiota.Builder.KiotaBuilder[0]
      step 2 - parsing the document - took 00:00:00.0679186
dbug: Kiota.Builder.KiotaBuilder[0]
      step 3 - updating generation configuration from kiota extension - took 00:00:00.0000607
dbug: Kiota.Builder.KiotaBuilder[0]
      step 4 - filtering API paths with patterns - took 00:00:00.0029692
info: Kiota.Builder.KiotaBuilder[0]
      Client root URL set to https://mytodos.doesnotexist
dbug: Kiota.Builder.KiotaBuilder[0]
      step 5 - checking whether the output should be updated - took 00:00:00.0074799
dbug: Kiota.Builder.KiotaBuilder[0]
      step 6 - create uri space - took 00:00:00.0023193
dbug: Kiota.Builder.KiotaBuilder[0]
      InitializeInheritanceIndex 00:00:00.0021671
warn: Kiota.Builder.KiotaBuilder[0]
      Discriminator Component1 is not inherited from ExampleWithSingleOneOfWithTypeObject.
dbug: Kiota.Builder.KiotaBuilder[0]
      CreateRequestBuilderClass 00:00:00
dbug: Kiota.Builder.KiotaBuilder[0]
      MapTypeDefinitions 00:00:00.0034938
info: Kiota.Builder.KiotaBuilder[0]
      Removing unused model Component1 as it is not referenced by the client API surface
dbug: Kiota.Builder.KiotaBuilder[0]
      TrimInheritedModels 00:00:00
dbug: Kiota.Builder.KiotaBuilder[0]
      CleanUpInternalState 00:00:00
dbug: Kiota.Builder.KiotaBuilder[0]
      step 7 - create source model - took 00:00:00.0405963
dbug: Kiota.Builder.KiotaBuilder[0]
      14ms: Language refinement applied
dbug: Kiota.Builder.KiotaBuilder[0]
      step 8 - refine by language - took 00:00:00.0147122
dbug: Kiota.Builder.KiotaBuilder[0]
      step 9 - writing files - took 00:00:00.0188196
info: Kiota.Builder.KiotaBuilder[0]
      loaded description from local source
dbug: Kiota.Builder.KiotaBuilder[0]
      step 10 - writing lock file - took 00:00:00.0083907
Generation completed successfully
Client base url set to https://mytodos.doesnotexist
dbug: Kiota.Builder.KiotaBuilder[0]
      Api manifest path: <repo_path>\kiota-type-object-one-of-issue\apimanifest.json

Hint: use the info command to get the list of dependencies you need to add to your project.
Example: kiota info -d "<repo_path>\kiota-type-object-one-of-issue\.\DiscriminatorProblemSampleSimple.yaml" -l CSharp

Hint: use the --include-path and --exclude-path options with glob patterns to filter the paths generated.
Example: kiota generate --include-path "**/foo" -d "<repo_path>\kiota-type-object-one-of-issue\.\DiscriminatorProblemSampleSimple.yaml"


```
</details>


### Other information

_No response_

Pull request:
fixes #5808

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17076aa315..224f085f85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,8 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - Fixed python generation in scenarios with opening/closing tags for code comments. [#5636](https://github.com/microsoft/kiota/issues/5636)
-- Fixed Python error when a class inherits from a base class and implements an interface. [5637](https://github.com/microsoft/kiota/issues/5637)
-- Fix anyOf/oneOf generation in TypeScript. [5353](https://github.com/microsoft/kiota/issues/5353)
+- Fixed Python error when a class inherits from a base class and implements an interface. [#5637](https://github.com/microsoft/kiota/issues/5637)
+- Fixed a bug where one/any schemas with single schema entries would be missing properties. [#5808](https://github.com/microsoft/kiota/issues/5808)
+- Fixed anyOf/oneOf generation in TypeScript. [5353](https://github.com/microsoft/kiota/issues/5353)
 - Fixed invalid code in Php caused by "*/*/" in property description. [5635](https://github.com/microsoft/kiota/issues/5635)
 - Fixed TypeScript generation error when generating usings from shaken serializers. [#5634](https://github.com/microsoft/kiota/issues/5634)
 
diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
index c5ed4b24e9..890231960c 100644
--- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
@@ -68,9 +68,9 @@ public static bool HasAnyProperty(this OpenApiSchema? schema)
     {
         return schema?.Properties is { Count: > 0 };
     }
-    public static bool IsInclusiveUnion(this OpenApiSchema? schema)
+    public static bool IsInclusiveUnion(this OpenApiSchema? schema, uint exclusiveMinimumNumberOfEntries = 1)
     {
-        return schema?.AnyOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > 1;
+        return schema?.AnyOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > exclusiveMinimumNumberOfEntries;
         // so we don't consider any of object/nullable as a union type
     }
 
@@ -89,6 +89,36 @@ public static bool IsInherited(this OpenApiSchema? schema)
         return schema.MergeIntersectionSchemaEntries(schemasToExclude, true, filter);
     }
 
+    internal static OpenApiSchema? MergeInclusiveUnionSchemaEntries(this OpenApiSchema? schema)
+    {
+        if (schema is null || !schema.IsInclusiveUnion(0)) return null;
+        var result = new OpenApiSchema(schema);
+        result.AnyOf.Clear();
+        foreach (var subSchema in schema.AnyOf)
+        {
+            foreach (var property in subSchema.Properties)
+            {
+                result.Properties.TryAdd(property.Key, property.Value);
+            }
+        }
+        return result;
+    }
+
+    internal static OpenApiSchema? MergeExclusiveUnionSchemaEntries(this OpenApiSchema? schema)
+    {
+        if (schema is null || !schema.IsExclusiveUnion(0)) return null;
+        var result = new OpenApiSchema(schema);
+        result.OneOf.Clear();
+        foreach (var subSchema in schema.OneOf)
+        {
+            foreach (var property in subSchema.Properties)
+            {
+                result.Properties.TryAdd(property.Key, property.Value);
+            }
+        }
+        return result;
+    }
+
     internal static OpenApiSchema? MergeIntersectionSchemaEntries(this OpenApiSchema? schema, HashSet<OpenApiSchema>? schemasToExclude = default, bool overrideIntersection = false, Func<OpenApiSchema, bool>? filter = default)
     {
         if (schema is null) return null;
@@ -123,9 +153,9 @@ public static bool IsIntersection(this OpenApiSchema? schema)
         return meaningfulSchemas?.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) > 1 || meaningfulSchemas?.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) > 1;
     }
 
-    public static bool IsExclusiveUnion(this OpenApiSchema? schema)
+    public static bool IsExclusiveUnion(this OpenApiSchema? schema, uint exclusiveMinimumNumberOfEntries = 1)
     {
-        return schema?.OneOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > 1;
+        return schema?.OneOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > exclusiveMinimumNumberOfEntries;
         // so we don't consider one of object/nullable as a union type
     }
     private static readonly HashSet<string> oDataTypes = new(StringComparer.OrdinalIgnoreCase) {
diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs
index fd4e0887c2..40c3207ff9 100644
--- a/src/Kiota.Builder/KiotaBuilder.cs
+++ b/src/Kiota.Builder/KiotaBuilder.cs
@@ -1895,6 +1895,16 @@ private CodeElement AddModelDeclarationIfDoesntExist(OpenApiUrlTreeNode currentN
                 // multiple allOf entries that do not translate to inheritance
                 return createdClass;
             }
+            else if (schema.MergeInclusiveUnionSchemaEntries() is { } iUMergedSchema &&
+                AddModelClass(currentNode, iUMergedSchema, declarationName, currentNamespace, currentOperation, inheritsFrom) is CodeClass uICreatedClass)
+            {
+                return uICreatedClass;
+            }
+            else if (schema.MergeExclusiveUnionSchemaEntries() is { } eUMergedSchema &&
+                AddModelClass(currentNode, eUMergedSchema, declarationName, currentNamespace, currentOperation, inheritsFrom) is CodeClass uECreatedClass)
+            {
+                return uECreatedClass;
+            }
             return AddModelClass(currentNode, schema, declarationName, currentNamespace, currentOperation, inheritsFrom);
         }
         return existingDeclaration;
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
index 2bee2503fb..b2a97e360b 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
@@ -140,7 +140,7 @@ private void WriteFactoryMethodBodyForUnionModel(CodeMethod codeElement, CodeCla
                 }
                 else if (propertyType.TypeDefinition is CodeClass && propertyType.IsCollection || propertyType.TypeDefinition is null || propertyType.TypeDefinition is CodeEnum)
                 {
-                    var typeName = conventions.GetTypeString(propertyType, codeElement, true, false);
+                    var typeName = conventions.GetTypeString(propertyType, codeElement, true, propertyType.TypeDefinition is CodeEnum && propertyType.CollectionKind is not CodeTypeBase.CodeTypeCollectionKind.None);
                     var valueVarName = $"{property.Name.ToFirstCharacterLowerCase()}Value";
                     writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name.ToFirstCharacterLowerCase()}.{GetDeserializationMethodName(propertyType, codeElement)} is {typeName} {valueVarName})");
                     writer.WriteBlock(lines: $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()} = {valueVarName};");
@@ -161,7 +161,7 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement,
         {
             if (property.Type is CodeType propertyType)
             {
-                var typeName = conventions.GetTypeString(propertyType, codeElement, true, false);
+                var typeName = conventions.GetTypeString(propertyType, codeElement, true, propertyType.TypeDefinition is CodeEnum && propertyType.CollectionKind is not CodeTypeBase.CodeTypeCollectionKind.None);
                 var valueVarName = $"{property.Name.ToFirstCharacterLowerCase()}Value";
                 writer.WriteLine($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name.ToFirstCharacterLowerCase()}.{GetDeserializationMethodName(propertyType, codeElement)} is {typeName} {valueVarName})");
                 writer.WriteBlock(lines: $"{ResultVarName}.{property.Name.ToFirstCharacterUpperCase()} = {valueVarName};");
diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
index 542be4f681..ea026bd4c8 100644
--- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
@@ -195,13 +195,17 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement,
                     WriteCollectionCast(propertyTypeImportName, valueVarName, "cast", writer, isInterfaceType ? string.Empty : "*", !isInterfaceType);
                     valueVarName = "cast";
                 }
-                else if (propertyType.TypeDefinition is CodeClass || propertyType.TypeDefinition is CodeInterface)
+                else if (propertyType.TypeDefinition is CodeClass || propertyType.TypeDefinition is CodeInterface || propertyType.TypeDefinition is CodeEnum)
                 {
+                    if (propertyType.TypeDefinition is CodeEnum)
+                    {
+                        propertyTypeImportName = conventions.GetTypeString(property.Type, parentClass, false, true);
+                    }
                     writer.StartBlock($"if {GetTypeAssertion(valueVarName, propertyTypeImportName, "cast", "ok")}; ok {{");
                     valueVarName = "cast";
                 }
                 writer.WriteLine($"{ResultVarName}.{property.Setter!.Name.ToFirstCharacterUpperCase()}({valueVarName})");
-                if (!propertyType.IsCollection && (propertyType.TypeDefinition is CodeClass || propertyType.TypeDefinition is CodeInterface))
+                if (!propertyType.IsCollection && (propertyType.TypeDefinition is CodeClass || propertyType.TypeDefinition is CodeInterface || propertyType.TypeDefinition is CodeEnum))
                     writer.CloseBlock();
                 writer.DecreaseIndent();
             }


Test patch:
diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
index 606bbe5c62..a62089926f 100644
--- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
+++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
@@ -8536,6 +8536,182 @@ public async Task InheritanceWithAllOfBaseClassNoAdditionalPropertiesAsync()
         Assert.Equal("baseDirectoryObject", link.StartBlock.Inherits.Name);
     }
 
+    [Fact]
+    public async Task ExclusiveUnionSingleEntriesMergingAsync()
+    {
+        var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
+        await using var fs = await GetDocumentStreamAsync(
+"""
+openapi: 3.0.0
+info:
+  title: "Generator not generating oneOf if the containing schema has type: object"
+  version: "1.0.0"
+servers:
+  - url: https://mytodos.doesnotexist/
+paths:
+  /uses-components:
+    post:
+      description: Return something
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/UsesComponents"
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/UsesComponents"
+components:
+  schemas:
+    ExampleWithSingleOneOfWithTypeObject:
+      type: object
+      oneOf:
+        - $ref: "#/components/schemas/Component1"
+      discriminator:
+        propertyName: objectType
+    ExampleWithSingleOneOfWithoutTypeObject:
+      oneOf:
+        - $ref: "#/components/schemas/Component2"
+      discriminator:
+        propertyName: objectType
+
+    UsesComponents:
+      type: object
+      properties:
+        component_with_single_oneof_with_type_object:
+          $ref: "#/components/schemas/ExampleWithSingleOneOfWithTypeObject"
+        component_with_single_oneof_without_type_object:
+          $ref: "#/components/schemas/ExampleWithSingleOneOfWithoutTypeObject"
+
+    Component1:
+      type: object
+      required:
+        - objectType
+      properties:
+        objectType:
+          type: string
+        one:
+          type: string
+
+    Component2:
+      type: object
+      required:
+        - objectType
+      properties:
+        objectType:
+          type: string
+        two:
+          type: string
+""");
+        var mockLogger = new Mock<ILogger<KiotaBuilder>>();
+        var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient);
+        var document = await builder.CreateOpenApiDocumentAsync(fs);
+        var node = builder.CreateUriSpace(document);
+        var codeModel = builder.CreateSourceModel(node);
+
+        // Verify that all three classes referenced by the discriminator inherit from baseDirectoryObject
+        var withObjectClass = codeModel.FindChildByName<CodeClass>("ExampleWithSingleOneOfWithTypeObject");
+        Assert.NotNull(withObjectClass);
+        var oneProperty = withObjectClass.FindChildByName<CodeProperty>("one", false);
+        Assert.NotNull(oneProperty);
+
+        var withoutObjectClass = codeModel.FindChildByName<CodeClass>("Component2");
+        Assert.NotNull(withObjectClass);
+        var twoProperty = withoutObjectClass.FindChildByName<CodeProperty>("two", false);
+        Assert.NotNull(twoProperty);
+    }
+
+    [Fact]
+    public async Task InclusiveUnionSingleEntriesMergingAsync()
+    {
+        var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
+        await using var fs = await GetDocumentStreamAsync(
+"""
+openapi: 3.0.0
+info:
+  title: "Generator not generating anyOf if the containing schema has type: object"
+  version: "1.0.0"
+servers:
+  - url: https://mytodos.doesnotexist/
+paths:
+  /uses-components:
+    post:
+      description: Return something
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/UsesComponents"
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/UsesComponents"
+components:
+  schemas:
+    ExampleWithSingleOneOfWithTypeObject:
+      type: object
+      anyOf:
+        - $ref: "#/components/schemas/Component1"
+      discriminator:
+        propertyName: objectType
+    ExampleWithSingleOneOfWithoutTypeObject:
+      anyOf:
+        - $ref: "#/components/schemas/Component2"
+      discriminator:
+        propertyName: objectType
+
+    UsesComponents:
+      type: object
+      properties:
+        component_with_single_oneof_with_type_object:
+          $ref: "#/components/schemas/ExampleWithSingleOneOfWithTypeObject"
+        component_with_single_oneof_without_type_object:
+          $ref: "#/components/schemas/ExampleWithSingleOneOfWithoutTypeObject"
+
+    Component1:
+      type: object
+      required:
+        - objectType
+      properties:
+        objectType:
+          type: string
+        one:
+          type: string
+
+    Component2:
+      type: object
+      required:
+        - objectType
+      properties:
+        objectType:
+          type: string
+        two:
+          type: string
+""");
+        var mockLogger = new Mock<ILogger<KiotaBuilder>>();
+        var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient);
+        var document = await builder.CreateOpenApiDocumentAsync(fs);
+        var node = builder.CreateUriSpace(document);
+        var codeModel = builder.CreateSourceModel(node);
+
+        // Verify that all three classes referenced by the discriminator inherit from baseDirectoryObject
+        var withObjectClass = codeModel.FindChildByName<CodeClass>("ExampleWithSingleOneOfWithTypeObject");
+        Assert.NotNull(withObjectClass);
+        var oneProperty = withObjectClass.FindChildByName<CodeProperty>("one", false);
+        Assert.NotNull(oneProperty);
+
+        var withoutObjectClass = codeModel.FindChildByName<CodeClass>("Component2");
+        Assert.NotNull(withObjectClass);
+        var twoProperty = withoutObjectClass.FindChildByName<CodeProperty>("two", false);
+        Assert.NotNull(twoProperty);
+    }
+
     [Fact]
     public async Task NestedIntersectionTypeAllOfAsync()
     {