From e469e0082a032f98b9e49047d19ac69807e129a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Fri, 26 Apr 2024 13:51:31 +0200 Subject: [PATCH] STJ: Fix dynamic dispatch in deserialization --- .../ViewModelSerializationMap.cs | 15 +++++------ src/Tests/ViewModel/SerializerTests.cs | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Framework/Framework/ViewModel/Serialization/ViewModelSerializationMap.cs b/src/Framework/Framework/ViewModel/Serialization/ViewModelSerializationMap.cs index d585be850d..36f9acc504 100644 --- a/src/Framework/Framework/ViewModel/Serialization/ViewModelSerializationMap.cs +++ b/src/Framework/Framework/ViewModel/Serialization/ViewModelSerializationMap.cs @@ -813,27 +813,28 @@ private static void SerializeValue(Utf8JsonWriter writer, JsonSerializer } public static readonly MethodInfo DeserializeViewModelDynamicMethod = typeof(JsonSerializationCodegenFragments).GetMethod(nameof(DeserializeViewModelDynamic), BindingFlags.NonPublic | BindingFlags.Static).NotNull(); - private static TVM? DeserializeViewModelDynamic(ref Utf8JsonReader reader, JsonSerializerOptions options, TVM? existingValue, bool populate, ViewModelJsonConverter factory, ViewModelJsonConverter.VMConverter? defaultConverter, DotvvmSerializationState state) + private static TVM? DeserializeViewModelDynamic(ref Utf8JsonReader reader, JsonSerializerOptions options, TVM? existingValue, bool populate, ViewModelJsonConverter factory, ViewModelJsonConverter.VMConverter defaultConverter, DotvvmSerializationState state) where TVM: class { if (reader.TokenType == JsonTokenType.Null) return default; - if (existingValue is null && defaultConverter is null) + if (existingValue is null) { - throw new Exception($"Cannot deserialize {typeof(TVM).ToCode()} dynamically, originalValue is null and type is abstract."); + return defaultConverter.Read(ref reader, typeof(TVM), options, state); } - if (defaultConverter is {} && (existingValue is null || existingValue.GetType() == typeof(TVM))) + var realType = existingValue?.GetType() ?? typeof(TVM); + if (defaultConverter is {} && realType == typeof(TVM)) { return populate && existingValue is {} ? defaultConverter.Populate(ref reader, options, existingValue, state) : defaultConverter.Read(ref reader, typeof(TVM), options, state); } - var converter = factory.GetConverterCached(typeof(TVM)); - return populate ? (TVM?)converter.PopulateUntyped(ref reader, typeof(TVM), existingValue, options, state) - : (TVM?)converter.ReadUntyped(ref reader, typeof(TVM), options, state); + var converter = factory.GetConverterCached(realType); + return populate ? (TVM?)converter.PopulateUntyped(ref reader, realType, existingValue, options, state) + : (TVM?)converter.ReadUntyped(ref reader, realType, options, state); } public static readonly MethodInfo ReadEncryptedValueMethod = typeof(JsonSerializationCodegenFragments).GetMethod(nameof(ReadEncryptedValue), BindingFlags.NonPublic | BindingFlags.Static).NotNull(); diff --git a/src/Tests/ViewModel/SerializerTests.cs b/src/Tests/ViewModel/SerializerTests.cs index ceca3d41c6..1330cb8331 100644 --- a/src/Tests/ViewModel/SerializerTests.cs +++ b/src/Tests/ViewModel/SerializerTests.cs @@ -690,6 +690,29 @@ public void InterfaceSerialization_Static() XAssert.Equal(obj.Property2, obj2.Value.Property2); } + [TestMethod] + public void InterfaceSerialization_Dynamic() + { + var obj = new VMWithInterface() { + Property1 = "x", + Property2 = "y", + Property3 = "z" + }; + var jsonStr = Serialize(new DefaultDispatchVMContainer { Value = obj }, out var _, false); + Console.WriteLine(jsonStr); + var json = JsonNode.Parse(jsonStr).AsObject()["Value"].AsObject(); + XAssert.Equal("x", (string)json["Property1"]); + XAssert.Equal("y", (string)json["Property2"]); + XAssert.Equal("z", (string)json["Property3"]); + + var obj2 = new DefaultDispatchVMContainer { Value = new VMWithInterface() }; + var obj2Populated = PopulateViewModel(jsonStr, obj2); + Assert.AreSame(obj2, obj2Populated); + XAssert.Equal(obj.Property1, obj2.Value.Property1); + XAssert.Equal(obj.Property2, obj2.Value.Property2); + XAssert.Equal(obj.Property3, ((VMWithInterface)obj2.Value).Property3); + } + class VMWithInterface: IVMInterface1 { public string Property1 { get; set; } @@ -935,7 +958,7 @@ class StaticDispatchVMContainer class DefaultDispatchVMContainer { - [Bind(AllowDynamicDispatch = false)] + [Bind(Name = "Value")] // make sure that the attribute presence does not affect the default behavior public TStatic Value { get; set; } } }