diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index d67edf8103d..4159758ce6a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -1074,7 +1074,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi var propertyExpression = parameter.Property?.AsVariableExpression ?? parameter.Field?.AsVariableExpression; var checkIfJsonPropEqualsName = new IfStatement(jsonProperty.NameEquals(propertySerializationName)) { - DeserializeProperty(propertyName!, propertyType!, wireInfo, propertyExpression!, jsonProperty, serializationAttributes, parameter.Property?.SerializationFormat) + DeserializeProperty(propertyName!, propertyType!, wireInfo, propertyExpression!, jsonProperty, serializationAttributes, wireInfo.SerializationFormat) }; propertyDeserializationStatements.Add(checkIfJsonPropEqualsName); } @@ -1754,7 +1754,7 @@ private MethodBodyStatement[] CreateWritePropertiesStatements(bool isDynamicMode continue; } - propertyStatements.Add(CreateWritePropertyStatement(property.WireInfo, property.Type, property.Name, property, property.SerializationFormat)); + propertyStatements.Add(CreateWritePropertyStatement(property.WireInfo, property.Type, property.Name, property, property.WireInfo.SerializationFormat)); } foreach (var field in _model.CanonicalView.Fields) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index bc61fe5f334..4c28452d689 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -477,5 +477,29 @@ public async Task CanChangePropertyNameAndRedefineOriginal() var file = writer.Write(); Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + + [Test] + public async Task CanPreserveArrayEncodingForCustomizedProperty() + { + var props = new[] + { + InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String), encode: ArrayKnownEncoding.CommaDelimited) + }; + + var inputModel = InputFactory.Model("Model", properties: props, usage: InputModelTypeUsage.Json); + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serializationProvider!, + name => name == "DeserializeModel" || name == "JsonModelWriteCore")); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty.cs new file mode 100644 index 00000000000..2d18b87d28f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty.cs @@ -0,0 +1,72 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample.Models; + +namespace Sample +{ + public partial class Model + { + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.Model)} does not support writing '{format}' format."); + } + if (global::Sample.Optional.IsCollectionDefined(Prop2)) + { + writer.WritePropertyName("prop1"u8); + writer.WriteStringValue(string.Join(",", Prop2)); + } + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + internal static global::Sample.Models.Model DeserializeModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + global::System.Collections.Generic.IList prop2 = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("prop1"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + continue; + } + string stringValue = prop.Value.GetString(); + prop2 = string.IsNullOrEmpty(stringValue) ? new global::System.Collections.Generic.List() : new global::System.Collections.Generic.List(stringValue.Split(',')); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new global::Sample.Models.Model((prop2 ?? new global::Sample.ChangeTrackingList()), additionalBinaryDataProperties); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty/Model.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty/Model.cs new file mode 100644 index 00000000000..23bc9276356 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanPreserveArrayEncodingForCustomizedProperty/Model.cs @@ -0,0 +1,13 @@ + +using SampleTypeSpec; +using System.Collections.Generic; +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + public partial class Model + { + [CodeGenMember("Prop1")] + public IList Prop2 { get; internal set; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/PropertyWireInformation.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/PropertyWireInformation.cs index b7a2e85b87a..603686c1c73 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/PropertyWireInformation.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/PropertyWireInformation.cs @@ -34,7 +34,7 @@ public PropertyWireInformation(SerializationFormat serializationFormat, bool isR /// /// The input model property. internal PropertyWireInformation(InputProperty inputProperty) - : base(CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty.Type), inputProperty.SerializedName) + : base(CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty), inputProperty.SerializedName) // TODO -- this is only temporary because we do not support other type of serialization, improvement tracking https://github.com/microsoft/typespec/issues/5861 { InputModelProperty? modelProperty = inputProperty as InputModelProperty; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs index d6ffb0fa6ed..6638e48c05a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs @@ -92,7 +92,7 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T } EnclosingType = enclosingType; - _serializationFormat = GetSerializationFormat(inputProperty); + _serializationFormat = CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty); _isRequiredNonNullableConstant = inputProperty.IsRequired && propertyType is { IsLiteral: true, IsNullable: false }; var propHasSetter = PropertyHasSetter(propertyType, inputProperty); MethodSignatureModifiers setterModifier = propHasSetter ? MethodSignatureModifiers.Public : MethodSignatureModifiers.None; @@ -335,21 +335,5 @@ public void Update( BuildDocs(); } } - - private SerializationFormat GetSerializationFormat(InputProperty inputProperty) - { - // Handle array encoding from InputModelProperty - if (inputProperty is InputModelProperty modelProperty && - inputProperty.Type is InputArrayType) - { - var arrayEncoding = modelProperty.Encode; - if (arrayEncoding.HasValue) - { - return arrayEncoding.Value.ToSerializationFormat(); - } - } - - return CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty.Type); - } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 12c7fea1699..c5f46a399d5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -385,6 +385,25 @@ or InputPrimitiveTypeKind.Int64 or InputPrimitiveTypeKind.UInt8 or InputPrimitiv _ => SerializationFormat.Default }; + /// + /// Retrieves the serialization format for a given input property. For array-typed properties + /// this checks the property-level before falling + /// back to . + /// + /// The to retrieve the serialization format for. + /// The for the input property. + internal SerializationFormat GetSerializationFormat(InputProperty inputProperty) + { + if (inputProperty is InputModelProperty modelProperty && + inputProperty.Type is InputArrayType && + modelProperty.Encode.HasValue) + { + return modelProperty.Encode.Value.ToSerializationFormat(); + } + + return GetSerializationFormat(inputProperty.Type); + } + /// /// The initialization type of list properties. This type should implement both and . /// diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs index 570585e3abe..2881aceeaa6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs @@ -278,7 +278,8 @@ public static InputModelProperty Property( string? summary = null, string? serializedName = null, string? doc = null, - InputSerializationOptions? serializationOptions = null) + InputSerializationOptions? serializationOptions = null, + ArrayKnownEncoding? encode = null) { serializationOptions ??= new InputSerializationOptions(); return new InputModelProperty( @@ -294,7 +295,8 @@ public static InputModelProperty Property( access: null, isDiscriminator: isDiscriminator, serializedName: serializedName ?? wireName ?? name.ToVariableName(), - serializationOptions: serializationOptions); + serializationOptions: serializationOptions, + encode: encode); } public static InputHeaderParameter HeaderParameter(