Improve JsonSerializer support for derived types (dotnet/corefx#40654)
authorLayomi Akinrinade <laakinri@microsoft.com>
Thu, 29 Aug 2019 15:08:43 +0000 (11:08 -0400)
committerGitHub <noreply@github.com>
Thu, 29 Aug 2019 15:08:43 +0000 (11:08 -0400)
Commit migrated from https://github.com/dotnet/corefx/commit/c59f5bf9badcda76581cd56cbfc5507f29fb022c

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ObjectModelCollections.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ObjectModelCollections.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 355a97e..ff0e38f 100644 (file)
@@ -138,7 +138,6 @@ namespace System.Text.Json
 
             if (!(typeof(IEnumerable).IsAssignableFrom(queryType)) ||
                 queryType == typeof(string) ||
-                queryType.IsAbstract ||
                 queryType.IsInterface ||
                 queryType.IsArray ||
                 IsNativelySupportedCollection(queryType))
@@ -191,18 +190,7 @@ namespace System.Text.Json
                 }
             }
 
-            // Try non-generic interfaces without add methods
-            if (typeof(IEnumerable).IsAssignableFrom(queryType))
-            {
-                return typeof(IEnumerable);
-            }
-            else if (typeof(ICollection).IsAssignableFrom(queryType))
-            {
-                return typeof(ICollection);
-            }
-
-            // No natively supported collection that we support as derived types was detected.
-            return queryType;
+            return typeof(IEnumerable);
         }
 
         public static bool IsDeserializedByAssigningFromList(Type type)
index c2a83d4..c39d8db 100644 (file)
@@ -178,9 +178,9 @@ namespace System.Text.Json
                         }
                         else if (ClassType == ClassType.Enumerable)
                         {
-                            // Else if it's an implementing type that is not assignable from IList.
+                            // Else if it's an implementing type whose runtime type is not assignable to IList.
                             if (DeclaredPropertyType != ImplementedPropertyType &&
-                                (!typeof(IList).IsAssignableFrom(DeclaredPropertyType) ||
+                                (!typeof(IList).IsAssignableFrom(RuntimePropertyType) ||
                                 ImplementedPropertyType == typeof(ArrayList) ||
                                 ImplementedPropertyType == typeof(IList)))
                             {
index 99ec09a..9987a51 100644 (file)
@@ -123,6 +123,15 @@ namespace System.Text.Json
 
         public override IEnumerable CreateDerivedEnumerableInstance(JsonPropertyInfo collectionPropertyInfo, IList sourceList, string jsonPath, JsonSerializerOptions options)
         {
+            // Implementing types that don't have default constructors are not supported for deserialization.
+            if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    collectionPropertyInfo.DeclaredPropertyType,
+                    collectionPropertyInfo.ParentClassType,
+                    collectionPropertyInfo.PropertyInfo);
+            }
+
             object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
 
             if (instance is IList instanceOfIList)
@@ -164,7 +173,8 @@ namespace System.Text.Json
                 return instanceOfQueue;
             }
 
-            // TODO: Use reflection to support types implementing Stack or Queue.
+            // TODO (https://github.com/dotnet/corefx/issues/40479):
+            // Use reflection to support types implementing Stack or Queue.
 
             throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
                 collectionPropertyInfo.DeclaredPropertyType,
@@ -174,6 +184,15 @@ namespace System.Text.Json
 
         public override object CreateDerivedDictionaryInstance(JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options)
         {
+            // Implementing types that don't have default constructors are not supported for deserialization.
+            if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    collectionPropertyInfo.DeclaredPropertyType,
+                    collectionPropertyInfo.ParentClassType,
+                    collectionPropertyInfo.PropertyInfo);
+            }
+
             object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
 
             if (instance is IDictionary instanceOfIDictionary)
@@ -199,7 +218,8 @@ namespace System.Text.Json
                 }
             }
 
-            // TODO: Use reflection to support types implementing SortedList and maybe immutable dictionaries.
+            // TODO (https://github.com/dotnet/corefx/issues/40479):
+            // Use reflection to support types implementing SortedList and maybe immutable dictionaries.
 
             // Types implementing SortedList and immutable dictionaries will fail here.
             throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ObjectModelCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ObjectModelCollections.cs
new file mode 100644 (file)
index 0000000..c69c61e
--- /dev/null
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.ObjectModel;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        [Fact]
+        public static void Read_ObjectModelCollection()
+        {
+            Collection<bool> c = JsonSerializer.Deserialize<Collection<bool>>("[true,false]");
+            Assert.Equal(2, c.Count);
+            Assert.True(c[0]);
+            Assert.False(c[1]);
+
+            // Regression test for https://github.com/dotnet/corefx/issues/40597.
+            ObservableCollection<bool> oc = JsonSerializer.Deserialize<ObservableCollection<bool>>("[true,false]");
+            Assert.Equal(2, oc.Count);
+            Assert.True(oc[0]);
+            Assert.False(oc[1]);
+
+            SimpleKeyedCollection kc = JsonSerializer.Deserialize<SimpleKeyedCollection>("[true]");
+            Assert.Equal(1, kc.Count);
+            Assert.True(kc[0]);
+        }
+
+        [Fact]
+        public static void Read_ObjectModelCollection_Throws()
+        {
+            // No default constructor.
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyCollection<bool>>("[true,false]"));
+            // No default constructor.
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyObservableCollection<bool>>("[true,false]"));
+            // No default constructor.
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyDictionary<string, bool>>(@"{""true"":false}"));
+
+            // Abstract types can't be instantiated. This means there's no default constructor, so the type is not supported for deserialization.
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<KeyedCollection<string, bool>>("[true]"));
+        }
+
+        public class SimpleKeyedCollection : KeyedCollection<string, bool>
+        {
+            protected override string GetKeyForItem(bool item)
+            {
+                return item.ToString();
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ObjectModelCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ObjectModelCollections.cs
new file mode 100644 (file)
index 0000000..8e37183
--- /dev/null
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        [Fact]
+        public static void Write_ObjectModelCollection()
+        {
+            Collection<bool> c = new Collection<bool>() { true, false };
+            Assert.Equal("[true,false]", JsonSerializer.Serialize(c));
+
+            ObservableCollection<bool> oc = new ObservableCollection<bool>() { true, false };
+            Assert.Equal("[true,false]", JsonSerializer.Serialize(oc));
+
+            SimpleKeyedCollection kc = new SimpleKeyedCollection() { true, false };
+            Assert.Equal("[true,false]", JsonSerializer.Serialize(kc));
+            Assert.Equal("[true,false]", JsonSerializer.Serialize<KeyedCollection<string, bool>>(kc));
+
+            ReadOnlyCollection<bool> roc = new ReadOnlyCollection<bool>(new List<bool> { true, false });
+            Assert.Equal("[true,false]", JsonSerializer.Serialize(oc));
+
+            ReadOnlyObservableCollection<bool> rooc = new ReadOnlyObservableCollection<bool>(oc);
+            Assert.Equal("[true,false]", JsonSerializer.Serialize(rooc));
+
+            ReadOnlyDictionary<string, bool> rod = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool> { ["true"] = false } );
+            Assert.Equal(@"{""true"":false}", JsonSerializer.Serialize(rod));
+        }
+    }
+}
index d06ef98..60a78ff 100644 (file)
     <Compile Include="Serialization\Value.ReadTests.GenericCollections.cs" />
     <Compile Include="Serialization\Value.ReadTests.ImmutableCollections.cs" />
     <Compile Include="Serialization\Value.ReadTests.NonGenericCollections.cs" />
+    <Compile Include="Serialization\Value.ReadTests.ObjectModelCollections.cs" />
     <Compile Include="Serialization\Value.WriteTests.cs" />
     <Compile Include="Serialization\Value.WriteTests.GenericCollections.cs" />
     <Compile Include="Serialization\Value.WriteTests.ImmutableCollections.cs" />
     <Compile Include="Serialization\Value.WriteTests.NonGenericCollections.cs" />
+    <Compile Include="Serialization\Value.WriteTests.ObjectModelCollections.cs" />
     <Compile Include="Serialization\WriteValueTests.cs" />
     <Compile Include="TestCaseType.cs" />
     <Compile Include="TestClasses.ClassWithComplexObjects.cs" />