Fix regression when serializing untyped polymorphic root-level custom converters...
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Wed, 19 Oct 2022 09:06:05 +0000 (10:06 +0100)
committerGitHub <noreply@github.com>
Wed, 19 Oct 2022 09:06:05 +0000 (10:06 +0100)
Fix #77173.

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs

index df4963e..975df8a 100644 (file)
@@ -1,8 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Text.Json.Serialization.Metadata;
-
 namespace System.Text.Json.Serialization
 {
     public partial class JsonConverter<T>
@@ -58,7 +56,7 @@ namespace System.Text.Json.Serialization
                     }
                 }
 
-                bool success = TryRead(ref reader, TypeToConvert, options, ref state, out T? value);
+                bool success = TryRead(ref reader, state.Current.JsonTypeInfo.Type, options, ref state, out T? value);
                 if (success)
                 {
                     // Read any trailing whitespace. This will throw if JsonCommentHandling=Disallow.
index e61319e..cbf48b4 100644 (file)
@@ -278,5 +278,64 @@ namespace System.Text.Json.Serialization.Tests
                 }
             }
         }
+
+        [Fact]
+        public static void PolymorphicBaseClassConverter_IsPassedCorrectTypeToConvertParameter()
+        {
+            // Regression test for https://github.com/dotnet/runtime/issues/77173
+            var options = new JsonSerializerOptions { Converters = { new PolymorphicBaseClassConverter() } };
+
+            // Sanity check -- returns converter for the base class.
+            JsonConverter converter = options.GetConverter(typeof(DerivedClass));
+            Assert.IsAssignableFrom<PolymorphicBaseClassConverter>(converter);
+
+            // Validate that the correct typeToConvert parameter is passed in all serialization contexts:
+            // 1. Typed root value.
+            // 2. Untyped root value (where the reported regression occured).
+            // 3. Nested values in POCOs, collections & dictionaries.
+
+            DerivedClass result = JsonSerializer.Deserialize<DerivedClass>("{}", options);
+            Assert.IsType<DerivedClass>(result);
+
+            object objResult = JsonSerializer.Deserialize("{}", typeof(DerivedClass), options);
+            Assert.IsType<DerivedClass>(objResult);
+
+            PocoWithDerivedClassProperty pocoResult = JsonSerializer.Deserialize<PocoWithDerivedClassProperty>("""{"Value":{}}""", options);
+            Assert.IsType<DerivedClass>(pocoResult.Value);
+
+            DerivedClass[] arrayResult = JsonSerializer.Deserialize<DerivedClass[]>("[{}]", options);
+            Assert.IsType<DerivedClass>(arrayResult[0]);
+
+            Dictionary<string, DerivedClass> dictResult = JsonSerializer.Deserialize<Dictionary<string, DerivedClass>>("""{"Value":{}}""", options);
+            Assert.IsType<DerivedClass>(dictResult["Value"]);
+        }
+
+        public class BaseClass
+        { }
+
+        public class DerivedClass : BaseClass
+        { }
+
+        public class PocoWithDerivedClassProperty
+        {
+            public DerivedClass Value { get; set; }
+        }
+
+        public class PolymorphicBaseClassConverter : JsonConverter<BaseClass>
+        {
+            public override bool CanConvert(Type typeToConvert) => typeof(BaseClass).IsAssignableFrom(typeToConvert);
+
+            public override BaseClass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                Assert.Equal(typeof(DerivedClass), typeToConvert);
+                reader.Skip();
+                return (BaseClass)Activator.CreateInstance(typeToConvert);
+            }
+
+            public override void Write(Utf8JsonWriter writer, BaseClass value, JsonSerializerOptions options)
+            {
+                throw new NotImplementedException();
+            }
+        }
     }
 }