Repository: microsoft/kiota (csharp)

Issue:
Wrong serializer used if array of string uses enum
Hello, we are facing a problem with an API that holds an array of strings which has specific allowed values defined by enum.

Such array is being transformed to a comma separated list rather than having a proper JSON serialization.

Portion of the JSON Schema we are using

```json
  "requestBody": {
        "content": {
            "application/json": {
                "schema": {
                    "additionalProperties": false,
                    "properties": {
                        "contexts": {
                            "items": {
                                "enum": [
                                    "company",
                                    "project"
                                ],
                                "type": "string"
                            },
                            "type": "array"
                        },
```

This JSON schema is correctly interpreted by Swagger UI

<img width="465" alt="immagine" src="https://github.com/user-attachments/assets/c6986b1c-5b06-4229-bcd8-db5bacfad742">


However when serializing the payload we get this result: 

```json
{
    contexts: 'company, project',
    name: 'extension name'
}
```

I've reproduced this issue in a test I have in my project where I'm intercepting the request produced by the generated client:

<img width="768" alt="Screenshot 2024-07-12 alle 17 08 03" src="https://github.com/user-attachments/assets/616f7a08-9f39-40ea-b20f-7269bc3a69cc">

After doing a bit of reverse engineering I've found this line that may be the culprint

<img width="897" alt="immagine" src="https://github.com/user-attachments/assets/6428c343-64b7-4bb5-b70b-0432ab3bd751">

As far as I understood, here the `contexts` field is being registered as an enum rather than an CollectionOfObjects and I think may be the cause of the serialization issue. 

By updating the OAS definition not to use the enum there is a visible change to the above row: https://github.com/mia-platform/console-sdk/pull/186/files#diff-edaa8455836441c91748a0db0ddb0ae4d819148908dd0da2de5b92c60de36d5aR378

see https://github.com/mia-platform/console-sdk/pull/186

```patch
- if(extensionsPutRequestBody.contexts)
-    writer.writeEnumValue<ExtensionsPutRequestBody_contexts>("contexts", ...extensionsPutRequestBody.contexts);
+writer.writeCollectionOfPrimitiveValues<string>("contexts", extensionsPutRequestBody.contexts);
```

Does this make sense, do you have any insights of something I may overlook?

Thanks

Pull request:
Closes https://github.com/microsoft/kiota-typescript/issues/1276

Fixes serialization of collection of enums in typescript

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5e2b7eb9c..f0454e9045 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added support for net9 only installations of the dotnet tool. [#5887](https://github.com/microsoft/kiota/issues/5887)
 
 ### Changed
+- Fixes serialization of collection of enums. [microsoft/kiota-typescript#1276](https://github.com/microsoft/kiota-typescript/issues/1276)
 
 ## [1.22.3]
 
@@ -24,7 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - Fixed a bug in the VS Code extension deeplink with the API Center extension [#6004](https://github.com/microsoft/kiota/issues/6004)
-
 - Drops Python 3.8 support by removing deprecated type aliases from generated code. [microsoft/kiota-python#349](https://github.com/microsoft/kiota-python/issues/349)
 - Removes superfluous inline imports in serializer methods in Python Generation. 
 
diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
index c21197ae14..4608d707aa 100644
--- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
+++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
@@ -391,7 +391,6 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro
         var serializationName = GetSerializationMethodName(codeProperty.Type, codeFunction.OriginalLocalMethod);
         var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty;
 
-
         if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null)
         {
             var serializeName = GetSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}");
@@ -409,16 +408,15 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro
     private void WritePropertySerializationStatement(CodeProperty codeProperty, string modelParamName, string? serializationName, string? defaultValueSuffix, CodeFunction codeFunction, LanguageWriter writer)
     {
         var isCollectionOfEnum = IsCollectionOfEnum(codeProperty);
-        var spreadOperator = isCollectionOfEnum ? "..." : string.Empty;
         var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase();
         var composedType = GetOriginalComposedType(codeProperty.Type);
 
-        if (!string.IsNullOrWhiteSpace(spreadOperator))
+        if (isCollectionOfEnum)
             writer.WriteLine($"if({modelParamName}.{codePropertyName})");
         if (composedType is not null && (composedType.IsComposedOfPrimitives(IsPrimitiveType) || composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType)))
             WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty);
         else
-            writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});");
+            writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix});");
     }
 
     private void WriteSerializationStatementForComposedTypeProperty(CodeComposedTypeBase composedType, string modelParamName, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string? serializeName)
@@ -431,8 +429,6 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType
     private void WriteComposedTypeIfClause(CodeComposedTypeBase composedType, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix)
     {
         var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase();
-        var isCollectionOfEnum = IsCollectionOfEnum(codeProperty);
-        var spreadOperator = isCollectionOfEnum ? "..." : string.Empty;
 
         bool isFirst = true;
         foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType)))
@@ -446,7 +442,7 @@ private void WriteComposedTypeIfClause(CodeComposedTypeBase composedType, CodeFu
                 ? $"{isElse}if (Array.isArray({modelParamName}.{codePropertyName}) && ({modelParamName}.{codePropertyName}).every(item => typeof item === '{nodeType}')) {{"
                 : $"{isElse}if ( typeof {modelParamName}.{codePropertyName} === \"{nodeType}\") {{");
 
-            writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix} as {nodeType});");
+            writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix} as {nodeType});");
             writer.CloseBlock();
             isFirst = false;
         }
@@ -518,7 +514,8 @@ private static CodeType GetCodeTypeForComposedType(CodeComposedTypeBase composed
     {
         return propType switch
         {
-            _ when propType.TypeDefinition is CodeEnum currentEnum => $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>",
+            _ when propType.TypeDefinition is CodeEnum currentEnum && !propType.IsCollection => $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags ? "[]" : string.Empty)}>",
+            _ when propType.TypeDefinition is CodeEnum currentEnum && propType.IsCollection => $"writeCollectionOfEnumValues<{currentEnum.Name.ToFirstCharacterUpperCase()}>",
             _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue",
             _ when propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None => propType.TypeDefinition == null ? $"writeCollectionOfPrimitiveValues<{propertyType}>" : "writeCollectionOfObjectValues",
             _ => null
@@ -617,7 +614,12 @@ private static string GetDefaultValueLiteralForProperty(CodeProperty codePropert
             var codeEnumOption = enumDefinition.Options.First(x =>
                 x.SymbolName.Equals(codeProperty.DefaultValue.Trim('"').CleanupSymbolName(),
                     StringComparison.OrdinalIgnoreCase));
-            return $"{enumDefinition.CodeEnumObject.Name.ToFirstCharacterUpperCase()}.{codeEnumOption.Name.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}";
+            var enumDefault = $"{enumDefinition.CodeEnumObject.Name.ToFirstCharacterUpperCase()}.{codeEnumOption.Name.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}";
+            if (!string.IsNullOrEmpty(enumDefault) && !enumDefault.EqualsIgnoreCase("\"null\"") && IsCollectionOfEnum(codeProperty))
+            {
+                enumDefault = "[" + enumDefault + "]";
+            }
+            return enumDefault;
         }
 
         // only string primitive should keep quotes


Test patch:
diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
index 79025ef0fa..221fed0af0 100644
--- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
@@ -377,6 +377,54 @@ public async Task WritesDeSerializerBodyWithDefaultValueAsync()
         Assert.Contains("?? EnumTypeWithOptionObject.SomeOption", result);
     }
     [Fact]
+    public async Task WritesSerializerBodyEnumCollectionAsync()
+    {
+        var parentClass = TestHelper.CreateModelClass(root, "parentClass");
+        TestHelper.AddSerializationPropertiesToModelClass(parentClass);
+        var propName = "propWithDefaultValue";
+        parentClass.AddProperty(new CodeProperty
+        {
+            Name = propName,
+            Kind = CodePropertyKind.Custom,
+            Type = new CodeType
+            {
+                Name = "string",
+            },
+        });
+        var propertyEnum = new CodeEnum
+        {
+            Name = "EnumTypeWithOption",
+            Parent = parentClass,
+        };
+        var enumOption = new CodeEnumOption() { Name = "SomeOption" };
+        propertyEnum.AddOption(enumOption);
+        var codeNamespace = parentClass.Parent as CodeNamespace;
+        codeNamespace.AddEnum(propertyEnum);
+        parentClass.AddProperty(new CodeProperty
+        {
+            Name = "propWithDefaultEnum",
+            DefaultValue = enumOption.Name,
+            Type = new CodeType
+            {
+                TypeDefinition = propertyEnum,
+                CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array,
+            }
+        });
+
+        await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root);
+        var serializerFunction = root.FindChildByName<CodeFunction>($"serialize{parentClass.Name.ToFirstCharacterUpperCase()}");
+        Assert.NotNull(serializerFunction);
+        var parentNS = serializerFunction.GetImmediateParentOfType<CodeNamespace>();
+        Assert.NotNull(parentNS);
+        var complexTypeDefinition = root.FindChildByName<CodeInterface>("SomeComplexType");
+        Assert.NotNull(complexTypeDefinition);
+        parentNS.TryAddCodeFile("foo", serializerFunction, parentClass, complexTypeDefinition);
+        writer.Write(serializerFunction);
+        var result = tw.ToString();
+        Assert.Contains("writeCollectionOfEnumValues<EnumTypeWithOption>(\"propWithDefaultEnum\"", result);
+        Assert.Contains("?? [EnumTypeWithOptionObject.SomeOption]", result);
+    }
+    [Fact]
     public async Task WritesInheritedSerializerBodyAsync()
     {
         var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript };