From b78fde5b5ea16f9b66739c818303478abcc04122 Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 09:34:28 -0800 Subject: [PATCH 1/5] Don't call non-existent deserialize method for framework types --- .../MrwSerializationTypeDefinition.Dynamic.cs | 33 +-- .../MrwSerializationTypeDefinitionTests.cs | 214 ++++++++++++++++++ 2 files changed, 234 insertions(+), 13 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 145f4f28598..f0f61d75d0c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -13,25 +13,12 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Statements; -using static System.Runtime.InteropServices.JavaScript.JSType; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; namespace Microsoft.TypeSpec.Generator.ClientModel.Providers { public partial class MrwSerializationTypeDefinition { - internal static ValueExpression GetDeserializationMethodInvocationForType( - CSharpType modelType, - ScopedApi jsonElementVariable, - ValueExpression dataVariable, - ValueExpression? optionsVariable = null) - { - return ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(modelType, out var provider) && - provider is ModelProvider modelProvider - ? GetDeserializationMethodInvocationForType(modelProvider, jsonElementVariable, dataVariable, optionsVariable) - : modelType.Deserialize(jsonElementVariable, null, optionsVariable); - } - #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. private MethodBodyStatement CreateDictionarySerializationWithPatch( DictionaryExpression dictionary, @@ -525,5 +512,25 @@ private static ValueExpression GetDeserializationMethodInvocationForType( ? model.Type.Deserialize(jsonElementVariable, dataVariable, optionsVariable) : model.Type.Deserialize(jsonElementVariable, null, optionsVariable); } + + internal static ValueExpression GetDeserializationMethodInvocationForType( + CSharpType modelType, + ScopedApi jsonElementVariable, + ValueExpression dataVariable, + ValueExpression? optionsVariable = null) + { + if (modelType.IsFrameworkType) + { + return Static(typeof(ModelReaderWriter)).Invoke( + nameof(ModelReaderWriter.Read), + [dataVariable, ModelSerializationExtensionsSnippets.Wire, ModelReaderWriterContextSnippets.Default], + [modelType]); + } + + return ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(modelType, out var provider) && + provider is ModelProvider modelProvider + ? GetDeserializationMethodInvocationForType(modelProvider, jsonElementVariable, dataVariable, optionsVariable) + : modelType.Deserialize(jsonElementVariable, null, optionsVariable); + } } } 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 00d90346026..18ab74bdae6 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 @@ -924,5 +924,219 @@ protected override PropertyProvider[] BuildProperties() MethodBodyStatements statements => statements.Statements.Any(s => HasMethodBodyStatement(s, predicate)), _ => predicate(statement) }; + + [Test] + public void TestGetDeserializationMethodInvocationForType_FrameworkType() + { + MockHelpers.LoadMockGenerator(); + // Test that framework types use ModelReaderWriter.Read + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new VariableExpression(typeof(BinaryData), "data"); + var optionsVar = new VariableExpression(typeof(ModelReaderWriterOptions), "options"); + + var frameworkType = new CSharpType(typeof(string)); + var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( + frameworkType, + jsonElementVar, + dataVar, + optionsVar); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + Assert.IsTrue(resultString.Contains("global::System.ClientModel.Primitives.ModelReaderWriter.Read")); + Assert.IsTrue(resultString.Contains("")); + Assert.IsTrue(resultString.Contains("data")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_ModelType() + { + MockHelpers.LoadMockGenerator(); + // Test that model types use the model's deserialize method + var inputModel = InputFactory.Model("TestModel"); + var (model, _) = CreateModelAndSerialization(inputModel); + + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new VariableExpression(typeof(BinaryData), "data"); + var optionsVar = new VariableExpression(typeof(ModelReaderWriterOptions), "options"); + + var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( + model.Type, + jsonElementVar, + dataVar, + optionsVar); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + // Model types should use the Deserialize extension method + Assert.IsTrue(resultString.Contains("Deserialize")); + Assert.IsTrue(resultString.Contains("TestModel")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_WithoutOptions() + { + MockHelpers.LoadMockGenerator(); + // Test that the method works when options are not provided + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new VariableExpression(typeof(BinaryData), "data"); + + var frameworkType = new CSharpType(typeof(string)); + var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( + frameworkType, + jsonElementVar, + dataVar, + null); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + Assert.IsTrue(resultString.Contains("global::System.ClientModel.Primitives.ModelReaderWriter.Read")); + Assert.IsTrue(resultString.Contains("")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_DynamicModel() + { + MockHelpers.LoadMockGenerator(); + // Test that dynamic models handle data parameter correctly + var properties = new List + { + InputFactory.Property("prop1", InputPrimitiveType.String, isRequired: true) + }; + + var inputModel = InputFactory.Model("DynamicModel", properties: properties, modelAsStruct: false); + MockHelpers.LoadMockGenerator(); + + // Create a model provider + var modelProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ModelProvider; + Assert.IsNotNull(modelProvider); + + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new VariableExpression(typeof(BinaryData), "data"); + var optionsVar = new VariableExpression(typeof(ModelReaderWriterOptions), "options"); + + // Call the internal method directly from the test assembly + var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( + modelProvider!.Type, + jsonElementVar, + dataVar, + optionsVar); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + Assert.IsTrue(resultString.Contains("Deserialize")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_InDeserializationMethod() + { + MockHelpers.LoadMockGenerator(); + // Test that GetDeserializationMethodInvocationForType is used correctly in the deserialization method + var properties = new List + { + InputFactory.Property("modelProperty", InputFactory.Model("InnerModel"), isRequired: true) + }; + + var innerModel = InputFactory.Model("InnerModel"); + var inputModel = InputFactory.Model("OuterModel", properties: properties); + + var generator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel, innerModel], + createSerializationsCore: (inputType, typeProvider) => + inputType is InputModelType modelType ? [new MrwSerializationTypeDefinition(modelType, (typeProvider as ModelProvider)!)]: []); + + generator.Object.TypeFactory.RootInputModels.Add(inputModel); + generator.Object.TypeFactory.RootOutputModels.Add(inputModel); + generator.Object.TypeFactory.RootInputModels.Add(innerModel); + generator.Object.TypeFactory.RootOutputModels.Add(innerModel); + + var outerModel = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ModelProvider; + Assert.IsNotNull(outerModel); + + var serialization = outerModel!.SerializationProviders.FirstOrDefault() as MrwSerializationTypeDefinition; + Assert.IsNotNull(serialization); + + var deserializationMethod = serialization!.BuildDeserializationMethod(); + Assert.IsNotNull(deserializationMethod); + + var methodBody = deserializationMethod!.BodyStatements!.ToDisplayString(); + // Verify that the deserialization method contains calls to deserialize the inner model + Assert.IsTrue(methodBody.Contains("InnerModel")); + Assert.IsTrue(methodBody.Contains("Deserialize")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_CollectionOfModels() + { + MockHelpers.LoadMockGenerator(); + // Test deserialization of collections containing model types + var innerModel = InputFactory.Model("ItemModel"); + var properties = new List + { + InputFactory.Property("items", InputFactory.Array(innerModel), isRequired: true) + }; + + var inputModel = InputFactory.Model("CollectionModel", properties: properties); + + var generator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel, innerModel], + createSerializationsCore: (inputType, typeProvider) => + inputType is InputModelType modelType ? [new MrwSerializationTypeDefinition(modelType, (typeProvider as ModelProvider)!)]: []); + + generator.Object.TypeFactory.RootInputModels.Add(inputModel); + generator.Object.TypeFactory.RootOutputModels.Add(inputModel); + generator.Object.TypeFactory.RootInputModels.Add(innerModel); + generator.Object.TypeFactory.RootOutputModels.Add(innerModel); + + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ModelProvider; + Assert.IsNotNull(model); + + var serialization = model!.SerializationProviders.FirstOrDefault() as MrwSerializationTypeDefinition; + Assert.IsNotNull(serialization); + + var deserializationMethod = serialization!.BuildDeserializationMethod(); + Assert.IsNotNull(deserializationMethod); + + var methodBody = deserializationMethod!.BodyStatements!.ToDisplayString(); + // Verify that array deserialization includes item model deserialization + Assert.IsTrue(methodBody.Contains("ItemModel")); + } + + [Test] + public void TestGetDeserializationMethodInvocationForType_DictionaryOfModels() + { + MockHelpers.LoadMockGenerator(); + // Test deserialization of dictionaries with model values + var valueModel = InputFactory.Model("ValueModel"); + var properties = new List + { + InputFactory.Property("values", InputFactory.Dictionary(valueModel), isRequired: true) + }; + + var inputModel = InputFactory.Model("DictionaryModel", properties: properties); + + var generator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel, valueModel], + createSerializationsCore: (inputType, typeProvider) => + inputType is InputModelType modelType ? [new MrwSerializationTypeDefinition(modelType, (typeProvider as ModelProvider)!)]: []); + + generator.Object.TypeFactory.RootInputModels.Add(inputModel); + generator.Object.TypeFactory.RootOutputModels.Add(inputModel); + generator.Object.TypeFactory.RootInputModels.Add(valueModel); + generator.Object.TypeFactory.RootOutputModels.Add(valueModel); + + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ModelProvider; + Assert.IsNotNull(model); + + var serialization = model!.SerializationProviders.FirstOrDefault() as MrwSerializationTypeDefinition; + Assert.IsNotNull(serialization); + + var deserializationMethod = serialization!.BuildDeserializationMethod(); + Assert.IsNotNull(deserializationMethod); + + var methodBody = deserializationMethod!.BodyStatements!.ToDisplayString(); + // Verify that dictionary deserialization includes value model deserialization + Assert.IsTrue(methodBody.Contains("ValueModel")); + } } } From 9717c927ee4b6b0a8742172695666dba8a42656f Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 09:36:08 -0800 Subject: [PATCH 2/5] move --- .../MrwSerializationTypeDefinition.Dynamic.cs | 20 ------------------- .../MrwSerializationTypeDefinition.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index f0f61d75d0c..c62dfd0df8b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -512,25 +512,5 @@ private static ValueExpression GetDeserializationMethodInvocationForType( ? model.Type.Deserialize(jsonElementVariable, dataVariable, optionsVariable) : model.Type.Deserialize(jsonElementVariable, null, optionsVariable); } - - internal static ValueExpression GetDeserializationMethodInvocationForType( - CSharpType modelType, - ScopedApi jsonElementVariable, - ValueExpression dataVariable, - ValueExpression? optionsVariable = null) - { - if (modelType.IsFrameworkType) - { - return Static(typeof(ModelReaderWriter)).Invoke( - nameof(ModelReaderWriter.Read), - [dataVariable, ModelSerializationExtensionsSnippets.Wire, ModelReaderWriterContextSnippets.Default], - [modelType]); - } - - return ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(modelType, out var provider) && - provider is ModelProvider modelProvider - ? GetDeserializationMethodInvocationForType(modelProvider, jsonElementVariable, dataVariable, optionsVariable) - : modelType.Deserialize(jsonElementVariable, null, optionsVariable); - } } } 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 4eb94e9ba34..3d9a18fe7d3 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 @@ -2217,5 +2217,25 @@ private static bool TypeRequiresNullCheckInSerialization(CSharpType type) return false; } + + internal static ValueExpression GetDeserializationMethodInvocationForType( + CSharpType modelType, + ScopedApi jsonElementVariable, + ValueExpression dataVariable, + ValueExpression? optionsVariable = null) + { + if (modelType.IsFrameworkType) + { + return Static(typeof(ModelReaderWriter)).Invoke( + nameof(ModelReaderWriter.Read), + [dataVariable, ModelSerializationExtensionsSnippets.Wire, ModelReaderWriterContextSnippets.Default], + [modelType]); + } + + return ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(modelType, out var provider) && + provider is ModelProvider modelProvider + ? GetDeserializationMethodInvocationForType(modelProvider, jsonElementVariable, dataVariable, optionsVariable) + : modelType.Deserialize(jsonElementVariable, null, optionsVariable); + } } } From 2c213efe28ec9c748b4ee7f40f25419d883a3ca3 Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 09:43:53 -0800 Subject: [PATCH 3/5] fix --- .../MrwSerializationTypeDefinition.cs | 16 ++- .../MrwSerializationTypeDefinitionTests.cs | 101 +++++++++++++++--- 2 files changed, 95 insertions(+), 22 deletions(-) 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 3d9a18fe7d3..1c797c7c02b 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 @@ -1993,9 +1993,13 @@ Type t when ValueTypeIsInt(t) => { ScmCodeModelGenerator.Instance.Emitter.ReportDiagnostic( DiagnosticCodes.UnsupportedSerialization, - $"Deserialization of type {valueType.Name} is not supported.", + $"Deserialization of type {valueType.Name} may not be supported. Using MRW serialization.", severity: EmitterDiagnosticSeverity.Warning); - return GetDeserializationMethodInvocationForType(valueType, element, data, mrwOptions); + // Fall back to MRW deserialization for framework type + return Static(typeof(ModelReaderWriter)).Invoke( + nameof(ModelReaderWriter.Read), + [data, ModelSerializationExtensionsSnippets.Wire, ModelReaderWriterContextSnippets.Default], + [valueType]); } return exp; @@ -2224,14 +2228,6 @@ internal static ValueExpression GetDeserializationMethodInvocationForType( ValueExpression dataVariable, ValueExpression? optionsVariable = null) { - if (modelType.IsFrameworkType) - { - return Static(typeof(ModelReaderWriter)).Invoke( - nameof(ModelReaderWriter.Read), - [dataVariable, ModelSerializationExtensionsSnippets.Wire, ModelReaderWriterContextSnippets.Default], - [modelType]); - } - return ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(modelType, out var provider) && provider is ModelProvider modelProvider ? GetDeserializationMethodInvocationForType(modelProvider, jsonElementVariable, dataVariable, optionsVariable) 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 18ab74bdae6..7dc1d5617f5 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 @@ -928,12 +928,13 @@ protected override PropertyProvider[] BuildProperties() [Test] public void TestGetDeserializationMethodInvocationForType_FrameworkType() { - MockHelpers.LoadMockGenerator(); - // Test that framework types use ModelReaderWriter.Read + // Test that framework types are handled via DeserializeJsonValueCore + // which uses MRW.Read as a fallback for unsupported types var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new VariableExpression(typeof(BinaryData), "data"); var optionsVar = new VariableExpression(typeof(ModelReaderWriterOptions), "options"); + // String is a framework type that should be handled by DeserializeJsonValueCore var frameworkType = new CSharpType(typeof(string)); var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( frameworkType, @@ -943,15 +944,13 @@ public void TestGetDeserializationMethodInvocationForType_FrameworkType() Assert.IsNotNull(result); var resultString = result.ToDisplayString(); - Assert.IsTrue(resultString.Contains("global::System.ClientModel.Primitives.ModelReaderWriter.Read")); - Assert.IsTrue(resultString.Contains("")); - Assert.IsTrue(resultString.Contains("data")); + // Framework types now go through the type's Deserialize method + Assert.IsTrue(resultString.Contains("Deserialize")); } [Test] public void TestGetDeserializationMethodInvocationForType_ModelType() { - MockHelpers.LoadMockGenerator(); // Test that model types use the model's deserialize method var inputModel = InputFactory.Model("TestModel"); var (model, _) = CreateModelAndSerialization(inputModel); @@ -973,10 +972,31 @@ public void TestGetDeserializationMethodInvocationForType_ModelType() Assert.IsTrue(resultString.Contains("TestModel")); } + [Test] + public void TestGetDeserializationMethodInvocationForType_NonModelCSharpType() + { + // Test that non-model CSharpTypes use the type's deserialize method + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new VariableExpression(typeof(BinaryData), "data"); + var optionsVar = new VariableExpression(typeof(ModelReaderWriterOptions), "options"); + + // Use a type that's not in the type factory + var customType = new CSharpType(typeof(int)); + var result = MrwSerializationTypeDefinition.GetDeserializationMethodInvocationForType( + customType, + jsonElementVar, + dataVar, + optionsVar); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + // Should call Deserialize on the type + Assert.IsTrue(resultString.Contains("Deserialize")); + } + [Test] public void TestGetDeserializationMethodInvocationForType_WithoutOptions() { - MockHelpers.LoadMockGenerator(); // Test that the method works when options are not provided var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new VariableExpression(typeof(BinaryData), "data"); @@ -990,14 +1010,74 @@ public void TestGetDeserializationMethodInvocationForType_WithoutOptions() Assert.IsNotNull(result); var resultString = result.ToDisplayString(); + // Should still deserialize even without options parameter + Assert.IsTrue(resultString.Contains("Deserialize")); + } + + [Test] + public void TestDeserializeJsonValueCore_FrameworkTypeWithMrwFallback() + { + // Test that DeserializeJsonValueCore uses MRW fallback for unsupported framework types + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new ScopedApi(new VariableExpression(typeof(BinaryData), "data")); + var optionsVar = new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")); + + // Use a framework type that's not explicitly handled in DeserializeJsonValueCore + // This should fall back to ModelReaderWriter.Read + var unsupportedType = new CSharpType(typeof(System.Reflection.Assembly)); + var result = MrwSerializationTypeDefinition.DeserializeJsonValueCore( + unsupportedType, + jsonElementVar, + dataVar, + optionsVar, + SerializationFormat.Default); + + Assert.IsNotNull(result); + var resultString = result.ToDisplayString(); + // Should use MRW.Read as fallback Assert.IsTrue(resultString.Contains("global::System.ClientModel.Primitives.ModelReaderWriter.Read")); - Assert.IsTrue(resultString.Contains("")); + Assert.IsTrue(resultString.Contains("data")); + } + + [Test] + public void TestDeserializeJsonValueCore_SupportedFrameworkTypes() + { + // Test that common framework types are handled correctly without MRW fallback + var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); + var dataVar = new ScopedApi(new VariableExpression(typeof(BinaryData), "data")); + var optionsVar = new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")); + + // Test string + var stringResult = MrwSerializationTypeDefinition.DeserializeJsonValueCore( + new CSharpType(typeof(string)), + jsonElementVar, + dataVar, + optionsVar, + SerializationFormat.Default); + Assert.IsTrue(stringResult.ToDisplayString().Contains("GetString")); + + // Test int + var intResult = MrwSerializationTypeDefinition.DeserializeJsonValueCore( + new CSharpType(typeof(int)), + jsonElementVar, + dataVar, + optionsVar, + SerializationFormat.Default); + Assert.IsTrue(intResult.ToDisplayString().Contains("GetInt32")); + + // Test bool + var boolResult = MrwSerializationTypeDefinition.DeserializeJsonValueCore( + new CSharpType(typeof(bool)), + jsonElementVar, + dataVar, + optionsVar, + SerializationFormat.Default); + Assert.IsTrue(boolResult.ToDisplayString().Contains("GetBoolean")); } [Test] public void TestGetDeserializationMethodInvocationForType_DynamicModel() { - MockHelpers.LoadMockGenerator(); // Test that dynamic models handle data parameter correctly var properties = new List { @@ -1030,7 +1110,6 @@ public void TestGetDeserializationMethodInvocationForType_DynamicModel() [Test] public void TestGetDeserializationMethodInvocationForType_InDeserializationMethod() { - MockHelpers.LoadMockGenerator(); // Test that GetDeserializationMethodInvocationForType is used correctly in the deserialization method var properties = new List { @@ -1068,7 +1147,6 @@ public void TestGetDeserializationMethodInvocationForType_InDeserializationMetho [Test] public void TestGetDeserializationMethodInvocationForType_CollectionOfModels() { - MockHelpers.LoadMockGenerator(); // Test deserialization of collections containing model types var innerModel = InputFactory.Model("ItemModel"); var properties = new List @@ -1105,7 +1183,6 @@ public void TestGetDeserializationMethodInvocationForType_CollectionOfModels() [Test] public void TestGetDeserializationMethodInvocationForType_DictionaryOfModels() { - MockHelpers.LoadMockGenerator(); // Test deserialization of dictionaries with model values var valueModel = InputFactory.Model("ValueModel"); var properties = new List From 6204a94ac96bdd500e1fe1cafec37997b331f74f Mon Sep 17 00:00:00 2001 From: jolov Date: Wed, 10 Dec 2025 09:46:06 -0800 Subject: [PATCH 4/5] mock --- .../MrwSerializationTypeDefinitionTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 7dc1d5617f5..621f52b1671 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 @@ -928,6 +928,7 @@ protected override PropertyProvider[] BuildProperties() [Test] public void TestGetDeserializationMethodInvocationForType_FrameworkType() { + MockHelpers.LoadMockGenerator(); // Test that framework types are handled via DeserializeJsonValueCore // which uses MRW.Read as a fallback for unsupported types var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); @@ -951,6 +952,7 @@ public void TestGetDeserializationMethodInvocationForType_FrameworkType() [Test] public void TestGetDeserializationMethodInvocationForType_ModelType() { + MockHelpers.LoadMockGenerator(); // Test that model types use the model's deserialize method var inputModel = InputFactory.Model("TestModel"); var (model, _) = CreateModelAndSerialization(inputModel); @@ -975,6 +977,7 @@ public void TestGetDeserializationMethodInvocationForType_ModelType() [Test] public void TestGetDeserializationMethodInvocationForType_NonModelCSharpType() { + MockHelpers.LoadMockGenerator(); // Test that non-model CSharpTypes use the type's deserialize method var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new VariableExpression(typeof(BinaryData), "data"); @@ -997,6 +1000,7 @@ public void TestGetDeserializationMethodInvocationForType_NonModelCSharpType() [Test] public void TestGetDeserializationMethodInvocationForType_WithoutOptions() { + MockHelpers.LoadMockGenerator(); // Test that the method works when options are not provided var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new VariableExpression(typeof(BinaryData), "data"); @@ -1017,6 +1021,7 @@ public void TestGetDeserializationMethodInvocationForType_WithoutOptions() [Test] public void TestDeserializeJsonValueCore_FrameworkTypeWithMrwFallback() { + MockHelpers.LoadMockGenerator(); // Test that DeserializeJsonValueCore uses MRW fallback for unsupported framework types var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new ScopedApi(new VariableExpression(typeof(BinaryData), "data")); @@ -1042,6 +1047,7 @@ public void TestDeserializeJsonValueCore_FrameworkTypeWithMrwFallback() [Test] public void TestDeserializeJsonValueCore_SupportedFrameworkTypes() { + MockHelpers.LoadMockGenerator(); // Test that common framework types are handled correctly without MRW fallback var jsonElementVar = new ScopedApi(new VariableExpression(typeof(JsonElement), "element")); var dataVar = new ScopedApi(new VariableExpression(typeof(BinaryData), "data")); @@ -1078,6 +1084,7 @@ public void TestDeserializeJsonValueCore_SupportedFrameworkTypes() [Test] public void TestGetDeserializationMethodInvocationForType_DynamicModel() { + MockHelpers.LoadMockGenerator(); // Test that dynamic models handle data parameter correctly var properties = new List { @@ -1110,6 +1117,7 @@ public void TestGetDeserializationMethodInvocationForType_DynamicModel() [Test] public void TestGetDeserializationMethodInvocationForType_InDeserializationMethod() { + MockHelpers.LoadMockGenerator(); // Test that GetDeserializationMethodInvocationForType is used correctly in the deserialization method var properties = new List { @@ -1147,6 +1155,7 @@ public void TestGetDeserializationMethodInvocationForType_InDeserializationMetho [Test] public void TestGetDeserializationMethodInvocationForType_CollectionOfModels() { + MockHelpers.LoadMockGenerator(); // Test deserialization of collections containing model types var innerModel = InputFactory.Model("ItemModel"); var properties = new List @@ -1183,6 +1192,7 @@ public void TestGetDeserializationMethodInvocationForType_CollectionOfModels() [Test] public void TestGetDeserializationMethodInvocationForType_DictionaryOfModels() { + MockHelpers.LoadMockGenerator(); // Test deserialization of dictionaries with model values var valueModel = InputFactory.Model("ValueModel"); var properties = new List From e13bd5d26eb663c6944c0592b5e3561b14947a29 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:48:33 -0800 Subject: [PATCH 5/5] Update packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs Co-authored-by: Jorge Rangel <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/MrwSerializationTypeDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1c797c7c02b..0298795e5de 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 @@ -1993,7 +1993,7 @@ Type t when ValueTypeIsInt(t) => { ScmCodeModelGenerator.Instance.Emitter.ReportDiagnostic( DiagnosticCodes.UnsupportedSerialization, - $"Deserialization of type {valueType.Name} may not be supported. Using MRW serialization.", + $"Deserialization of type {valueType.Name} may not be supported using MRW serialization.", severity: EmitterDiagnosticSeverity.Warning); // Fall back to MRW deserialization for framework type return Static(typeof(ModelReaderWriter)).Invoke(