From 93e76c51d7a4cdcd5b34dfcb83c03f22e1c7e721 Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 18:55:34 -0800 Subject: [PATCH 1/2] Fix for getting BinaryData from JsonElement --- .../ModelSerializationExtensionsDefinition.cs | 2 +- .../MrwSerializationTypeDefinition.cs | 41 ++++++++++++------- ...lSerializationExtensionsDefinitionTests.cs | 19 +++++++++ .../MrwSerializationTypeDefinitionTests.cs | 15 +++++++ .../GetUtf8BytesIsUsedForMrwFallback.cs | 23 +++++++++++ 5 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/MrwSerializationTypeDefinitionTests/GetUtf8BytesIsUsedForMrwFallback.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelSerializationExtensionsDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelSerializationExtensionsDefinition.cs index 93a9bdf7d73..5a839f27088 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelSerializationExtensionsDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelSerializationExtensionsDefinition.cs @@ -157,6 +157,7 @@ protected override MethodProvider[] BuildMethods() BuildWriteNumberValueMethodProvider(), BuildWriteObjectValueMethodGeneric(), BuildWriteObjectValueMethodProvider(), + BuildGetUtf8BytesMethodProvider(), .. BuildDynamicModelHelpers() ]; } @@ -576,7 +577,6 @@ private MethodProvider[] BuildDynamicModelHelpers() [ BuildSliceToStartOfPropertyNameMethodProvider(), BuildGetFirstPropertyNameMethodProvider(), - BuildGetUtf8BytesMethodProvider(), BuildTryGetIndexMethodProvider(), BuildGetRemainderMethodProvider() ]; 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 0298795e5de..e3b74b65f12 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 @@ -732,7 +732,7 @@ private MethodBodyStatement[] BuildDeserializationMethodBody() // Build the deserialization statements for each property ForEachStatement deserializePropertiesForEachStatement = new("prop", _jsonElementParameterSnippet.EnumerateObject(), out var prop) { - BuildDeserializePropertiesStatements(prop.As(), _dataParameter.As()) + BuildDeserializePropertiesStatements(prop.As()) }; var valueKindEqualsNullReturn = _isStruct ? Return(Default) : Return(Null); @@ -910,7 +910,7 @@ private static ValueExpression GetValueForSerializationConstructor(PropertyProvi return propertyProvider.AsVariableExpression; } - private List BuildDeserializePropertiesStatements(ScopedApi jsonProperty, ScopedApi data) + private List BuildDeserializePropertiesStatements(ScopedApi jsonProperty) { List propertyDeserializationStatements = []; Dictionary> additionalPropsValueKindBodyStatements = []; @@ -943,7 +943,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi // handle additional properties if (parameter.Property != null && parameter.Property != _additionalBinaryDataProperty.Value && parameter.Property.IsAdditionalProperties) { - AddAdditionalPropertiesValueKindStatements(additionalPropsValueKindBodyStatements, parameter.Property, jsonProperty, data); + AddAdditionalPropertiesValueKindStatements(additionalPropsValueKindBodyStatements, parameter.Property, jsonProperty); continue; } @@ -951,7 +951,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi // By default, we should only deserialize properties with wire info that are payload properties. // Those properties without wire info indicate they are not spec properties. - if (wireInfo == null || wireInfo.IsHttpMetadata == true) + if (wireInfo == null || wireInfo.IsHttpMetadata) { continue; } @@ -998,7 +998,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi if (_additionalBinaryDataProperty.Value != null) { var binaryDataDeserializationValue = ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue( - _additionalBinaryDataProperty.Value.Type.ElementType.FrameworkType, jsonProperty.Value(), _dataParameter.As(), _mrwOptionsParameterSnippet, SerializationFormat.Default); + _additionalBinaryDataProperty.Value.Type.ElementType, jsonProperty.Value(), _dataParameter.As(), _mrwOptionsParameterSnippet, SerializationFormat.Default); propertyDeserializationStatements.Add( _additionalBinaryDataProperty.Value.AsVariableExpression.AsDictionary(_additionalBinaryDataProperty.Value.Type).Add(jsonProperty.Name(), binaryDataDeserializationValue)); } @@ -1028,8 +1028,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi private void AddAdditionalPropertiesValueKindStatements( Dictionary> additionalPropsValueKindBodyStatements, PropertyProvider additionalPropertiesProperty, - ScopedApi jsonProperty, - ScopedApi data) + ScopedApi jsonProperty) { DictionaryExpression additionalPropsDict = additionalPropertiesProperty.AsVariableExpression.AsDictionary(additionalPropertiesProperty.Type); var valueType = additionalPropertiesProperty.Type.ElementType; @@ -1424,22 +1423,36 @@ private MethodBodyStatement DeserializeValue( } else { - value = CreateDeserializeValueExpression(valueType, serializationFormat, jsonElement, jsonElement.GetUtf8Bytes()); + value = CreateDeserializeValueExpression(valueType, serializationFormat, jsonElement); return MethodBodyStatement.Empty; } } - private ValueExpression CreateDeserializeValueExpression(CSharpType valueType, SerializationFormat serializationFormat, ScopedApi jsonElement, ScopedApi data) => - valueType switch + private ValueExpression CreateDeserializeValueExpression( + CSharpType valueType, + SerializationFormat serializationFormat, + ScopedApi jsonElement) + { + var data = jsonElement.GetUtf8Bytes(); + + return valueType switch { { IsFrameworkType: true } when valueType.FrameworkType == typeof(Nullable<>) => - ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue(valueType.Arguments[0].FrameworkType, jsonElement, _dataParameter.As(), _mrwOptionsParameterSnippet, serializationFormat), + ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue( + valueType.Arguments[0].FrameworkType, jsonElement, data, + _mrwOptionsParameterSnippet, serializationFormat), { IsFrameworkType: true } => - ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue(valueType.FrameworkType, jsonElement, _dataParameter.As(), _mrwOptionsParameterSnippet, serializationFormat), + ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue(valueType.FrameworkType, + jsonElement, data, _mrwOptionsParameterSnippet, + serializationFormat), { IsEnum: true } => - valueType.ToEnum(ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue(valueType.UnderlyingEnumType!, jsonElement, _dataParameter.As(), _mrwOptionsParameterSnippet, serializationFormat)), - _ => GetDeserializationMethodInvocationForType(valueType, jsonElement, data, _mrwOptionsParameterSnippet) + valueType.ToEnum(ScmCodeModelGenerator.Instance.TypeFactory.DeserializeJsonValue( + valueType.UnderlyingEnumType!, jsonElement, data, + _mrwOptionsParameterSnippet, serializationFormat)), + _ => GetDeserializationMethodInvocationForType(valueType, jsonElement, data, + _mrwOptionsParameterSnippet) }; + } private MethodBodyStatement CreateDeserializeDictionaryValueStatement( CSharpType dictionaryItemType, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelSerializationExtensionsDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelSerializationExtensionsDefinitionTests.cs index 3575f184e97..a9e2b5677a3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelSerializationExtensionsDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelSerializationExtensionsDefinitionTests.cs @@ -202,6 +202,25 @@ public void ValidateGetRequiredStringMethodIsGenerated() Assert.AreEqual(typeof(JsonElement), getRequiredStringMethod.Signature.Parameters[0].Type.FrameworkType); } + [Test] + public void ValidateGetUtf8BytesMethodIsGenerated() + { + MockHelpers.LoadMockGenerator(); + + var definition = new ModelSerializationExtensionsDefinition(); + var methods = definition.Methods; + + Assert.IsNotNull(methods); + var getUtf8BytesMethod = methods.SingleOrDefault(m => m.Signature.Name == "GetUtf8Bytes"); + Assert.IsNotNull(getUtf8BytesMethod, "GetUtf8Bytes method should be generated"); + Assert.AreEqual(typeof(BinaryData), getUtf8BytesMethod!.Signature.ReturnType?.FrameworkType); + Assert.IsTrue(getUtf8BytesMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.IsTrue(getUtf8BytesMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Static)); + Assert.IsTrue(getUtf8BytesMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Extension)); + Assert.AreEqual(1, getUtf8BytesMethod.Signature.Parameters.Count); + Assert.AreEqual(typeof(JsonElement), getUtf8BytesMethod.Signature.Parameters[0].Type.FrameworkType); + } + [Test] public void ValidateWriteStringValueMethodsAreGenerated() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs index 621f52b1671..d857b8162d3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs @@ -598,6 +598,21 @@ public void SerializedNameIsUsed(bool isRequired) Assert.AreEqual(Helpers.GetExpectedFromFile(isRequired.ToString()), methodBody); } + [Test] + public void GetUtf8BytesIsUsedForMrwFallback() + { + var property = InputFactory.Property( + "mockProperty", + InputFactory.Model("SomeExternalModel", external: new InputExternalTypeMetadata("System.IO.File", null, null))); + + var inputModel = InputFactory.Model("mockInputModel", properties: [property]); + var (_, serialization) = CreateModelAndSerialization(inputModel); + + var deserializationMethod = serialization.Methods.Single(m => m.Signature.Name.StartsWith("Deserialize")); + var methodBody = deserializationMethod.BodyStatements!.ToDisplayString(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), methodBody); + } + [Test] public void TestBuildDeserializationMethodNestedSARD() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/MrwSerializationTypeDefinitionTests/GetUtf8BytesIsUsedForMrwFallback.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/MrwSerializationTypeDefinitionTests/GetUtf8BytesIsUsedForMrwFallback.cs new file mode 100644 index 00000000000..0cd6864f675 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/MrwSerializationTypeDefinitionTests/GetUtf8BytesIsUsedForMrwFallback.cs @@ -0,0 +1,23 @@ +if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) +{ + return null; +} +global::System.IO.File mockProperty = default; +global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); +foreach (var prop in element.EnumerateObject()) +{ + if (prop.NameEquals("mockProperty"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + continue; + } + mockProperty = global::System.ClientModel.Primitives.ModelReaderWriter.Read(prop.Value.GetUtf8Bytes(), global::Sample.ModelSerializationExtensions.WireOptions, global::Sample.SampleContext.Default); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } +} +return new global::Sample.Models.MockInputModel(mockProperty, additionalBinaryDataProperties); From a049b038376ff80910a9c3ad1097785c40a24500 Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 20:26:03 -0800 Subject: [PATCH 2/2] regen --- .../Internal/ModelSerializationExtensions.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/ModelSerializationExtensions.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/ModelSerializationExtensions.cs index 25a919caece..099511239df 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/ModelSerializationExtensions.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/ModelSerializationExtensions.cs @@ -258,6 +258,15 @@ public static void WriteObjectValue(this Utf8JsonWriter writer, object value, Mo writer.WriteObjectValue(value, options); } + public static BinaryData GetUtf8Bytes(this JsonElement element) + { +#if NET9_0_OR_GREATER + return new global::System.BinaryData(global::System.Runtime.InteropServices.JsonMarshal.GetRawUtf8Value(element).ToArray()); +#else + return BinaryData.FromString(element.GetRawText()); +#endif + } + public static ReadOnlySpan SliceToStartOfPropertyName(this ReadOnlySpan jsonPath) { ReadOnlySpan local = jsonPath; @@ -304,15 +313,6 @@ public static string GetFirstPropertyName(this ReadOnlySpan jsonPath, out return key; } - public static BinaryData GetUtf8Bytes(this JsonElement element) - { -#if NET9_0_OR_GREATER - return new global::System.BinaryData(global::System.Runtime.InteropServices.JsonMarshal.GetRawUtf8Value(element).ToArray()); -#else - return BinaryData.FromString(element.GetRawText()); -#endif - } - public static bool TryGetIndex(this ReadOnlySpan indexSlice, out int index, out int bytesConsumed) { index = -1;