Improve serialization support for nested dictionaries
authorLayomi Akinrinade <laakinri@microsoft.com>
Thu, 12 Dec 2019 03:14:05 +0000 (19:14 -0800)
committerLayomi Akinrinade <laakinri@microsoft.com>
Thu, 12 Dec 2019 03:14:05 +0000 (19:14 -0800)
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs

index 71b438b..752ad36 100644 (file)
@@ -131,7 +131,7 @@ namespace System.Text.Json
 
         public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
         {
-            if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
+            if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TDeclaredProperty>> genericEnumerator)
             {
                 key = genericEnumerator.Current.Key;
                 value = genericEnumerator.Current.Value;
index 63039b7..1d68b6d 100644 (file)
@@ -2,7 +2,6 @@
 // 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;
 using System.Collections.Generic;
 using System.Diagnostics;
 
@@ -132,7 +131,7 @@ namespace System.Text.Json.Serialization
 
         public override void GetDictionaryKeyAndValueFromGenericDictionary(ref WriteStackFrame writeStackFrame, out string key, out object value)
         {
-            if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TRuntimeProperty>> genericEnumerator)
+            if (writeStackFrame.CollectionEnumerator is IEnumerator<KeyValuePair<string, TDeclaredProperty>> genericEnumerator)
             {
                 key = genericEnumerator.Current.Key;
                 value = genericEnumerator.Current.Value;
index c1163c4..e384ccf 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Reflection;
@@ -323,7 +324,7 @@ namespace System.Text.Json.Serialization.Tests
 
         public static IEnumerable<string> InvalidJsonForIntValue()
         {
-            yield return @"""1""" ;
+            yield return @"""1""";
             yield return "[";
             yield return "}";
             yield return @"[""1""]";
@@ -932,6 +933,102 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(JsonString, json);
         }
 
+        private interface IClass { }
+
+        private class MyClass : IClass { }
+
+        private class MyFactory : JsonConverterFactory
+        {
+            public override bool CanConvert(Type typeToConvert)
+            {
+                return typeToConvert == typeof(IClass) || typeToConvert == typeof(MyClass);
+            }
+
+            public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+            {
+                return new MyStuffConverter();
+            }
+        }
+
+        private class MyStuffConverter : JsonConverter<IClass>
+        {
+            public override IClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                return new MyClass();
+            }
+
+            public override void Write(Utf8JsonWriter writer, IClass value, JsonSerializerOptions options)
+            {
+                writer.WriteNumberValue(1);
+            }
+        }
+
+        private static IEnumerable<(Type, string)> NestedDictionaryTypeData()
+        {
+            string testJson = @"{""Key"":1}";
+
+            List<Type> genericDictTypes = new List<Type>()
+            {
+                typeof(IDictionary<,>),
+                typeof(Dictionary<,>),
+                typeof(ConcurrentDictionary<,>),
+            };
+
+            List<Type> nonGenericDictTypes = new List<Type>()
+            {
+                typeof(IDictionary),
+                typeof(Hashtable),
+            };
+
+            List<Type> baseDictionaryTypes = new List<Type>
+            {
+                typeof(IReadOnlyDictionary<string, MyClass>),
+                typeof(Dictionary<string, IClass>),
+                typeof(ConcurrentDictionary<string, int>),
+                typeof(ImmutableDictionary<string, object>),
+            };
+            baseDictionaryTypes.AddRange(nonGenericDictTypes);
+
+            int maxTestDepth = 4;
+
+            HashSet<(Type, string)> tests = new HashSet<(Type, string)>();
+
+            for (int i = 0; i < maxTestDepth; i++)
+            {
+                List<Type> newBaseTypes = new List<Type>();
+
+                foreach (Type testType in baseDictionaryTypes)
+                {
+                    tests.Add((testType, testJson));
+
+                    foreach (Type genericType in genericDictTypes)
+                    {
+                        newBaseTypes.Add(genericType.MakeGenericType(typeof(string), testType));
+                    }
+
+                    newBaseTypes.AddRange(nonGenericDictTypes);
+                }
+
+                baseDictionaryTypes = newBaseTypes;
+                testJson = @"{""Key"":" + testJson + "}";
+            }
+
+            return tests;
+        }
+
+        [Fact]
+        public static void NestedDictionariesRoundtrip()
+        {
+            JsonSerializerOptions options = new JsonSerializerOptions();
+            options.Converters.Add(new MyFactory());
+
+            foreach ((Type dictionaryType, string testJson) in NestedDictionaryTypeData())
+            {
+                object dict = JsonSerializer.Deserialize(testJson, dictionaryType, options);
+                Assert.Equal(testJson, JsonSerializer.Serialize(dict, options));
+            }
+        }
+
         [Fact]
         public static void DictionaryOfClasses()
         {
@@ -1107,7 +1204,7 @@ namespace System.Text.Json.Serialization.Tests
         public static void CustomEscapingOnPropertyNameAndValue()
         {
             var dict = new Dictionary<string, string>();
-            dict.Add("A\u046701","Value\u0467");
+            dict.Add("A\u046701", "Value\u0467");
 
             // Baseline with no escaping.
             var json = JsonSerializer.Serialize(dict);
@@ -1964,7 +2061,7 @@ namespace System.Text.Json.Serialization.Tests
             IEnumerator IEnumerable.GetEnumerator()
             {
                 // Create an incompatible converter.
-                return new int[] {100,200 }.GetEnumerator();
+                return new int[] { 100, 200 }.GetEnumerator();
             }
 
             public bool Remove(string key)