Skip to content

Commit

Permalink
STJ: Fix dynamic dispatch in deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Apr 26, 2024
1 parent 63d8471 commit e469e00
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -813,27 +813,28 @@ private static void SerializeValue<TValue>(Utf8JsonWriter writer, JsonSerializer
}

public static readonly MethodInfo DeserializeViewModelDynamicMethod = typeof(JsonSerializationCodegenFragments).GetMethod(nameof(DeserializeViewModelDynamic), BindingFlags.NonPublic | BindingFlags.Static).NotNull();
private static TVM? DeserializeViewModelDynamic<TVM>(ref Utf8JsonReader reader, JsonSerializerOptions options, TVM? existingValue, bool populate, ViewModelJsonConverter factory, ViewModelJsonConverter.VMConverter<TVM>? defaultConverter, DotvvmSerializationState state)
private static TVM? DeserializeViewModelDynamic<TVM>(ref Utf8JsonReader reader, JsonSerializerOptions options, TVM? existingValue, bool populate, ViewModelJsonConverter factory, ViewModelJsonConverter.VMConverter<TVM> 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();
Expand Down
25 changes: 24 additions & 1 deletion src/Tests/ViewModel/SerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IVMInterface1> { 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<IVMInterface1> { 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; }
Expand Down Expand Up @@ -935,7 +958,7 @@ class StaticDispatchVMContainer<TStatic>

class DefaultDispatchVMContainer<TStatic>
{
[Bind(AllowDynamicDispatch = false)]
[Bind(Name = "Value")] // make sure that the attribute presence does not affect the default behavior
public TStatic Value { get; set; }
}
}

0 comments on commit e469e00

Please sign in to comment.