Add support for serializing types that implement IDictionary<string, object> (dotnet...
authorLayomi Akinrinade <laakinri@microsoft.com>
Sun, 16 Jun 2019 10:57:34 +0000 (06:57 -0400)
committerGitHub <noreply@github.com>
Sun, 16 Jun 2019 10:57:34 +0000 (06:57 -0400)
* Add support for serializing types that implement IDictionary<string, object>

* Add test for string to string IDictionary

* fixup

* Address review feedback

* Rename dict -> dictionary

Commit migrated from https://github.com/dotnet/corefx/commit/e2e5fb23280b3962dcb0422b0df7930387c8ac24

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.Dictionary.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 3c535a4..9a00e5b 100644 (file)
@@ -29,14 +29,13 @@ namespace System.Text.Json
             // Convert non-immutable dictionary interfaces to concrete types.
             if (propertyType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary)
             {
-                // If a polymorphic case, we have to wait until run-time values are processed.
-                if (jsonInfo.ElementClassInfo.ClassType != ClassType.Unknown || propertyType == typeof(IDictionary))
+                JsonClassInfo elementClassInfo = jsonInfo.ElementClassInfo;
+                JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
+
+                Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType();
+                if (propertyType != newPropertyType)
                 {
-                    Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetDictionaryConcreteType();
-                    if (propertyType != newPropertyType)
-                    {
-                        jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options);
-                    }
+                    jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options);
                 }
             }
             else if (jsonInfo.ClassType == ClassType.Enumerable &&
index 91b6bdb..7fc3a9d 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;
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -31,7 +30,15 @@ namespace System.Text.Json
                     return true;
                 }
 
-                state.Current.Enumerator = ((IDictionary)enumerable).GetEnumerator();
+                if (enumerable is IDictionary dictionary)
+                {
+                    state.Current.Enumerator = dictionary.GetEnumerator();
+                }
+                else
+                {
+                    state.Current.Enumerator = enumerable.GetEnumerator();
+                }
+
                 state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer);
             }
 
index 544c24b..6efcfdf 100644 (file)
@@ -5,6 +5,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Immutable;
+using Newtonsoft.Json;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -158,7 +159,7 @@ namespace System.Text.Json.Serialization.Tests
         public static void DictionaryOfObject()
         {
             {
-                IDictionary obj = JsonSerializer.Parse<IDictionary>(@"{""Key1"":1}");
+                Dictionary<string, object> obj = JsonSerializer.Parse<Dictionary<string, object>>(@"{""Key1"":1}");
                 Assert.Equal(1, obj.Count);
                 JsonElement element = (JsonElement)obj["Key1"];
                 Assert.Equal(JsonValueType.Number, element.Type);
@@ -169,7 +170,7 @@ namespace System.Text.Json.Serialization.Tests
             }
 
             {
-                Dictionary<string, object> obj = JsonSerializer.Parse<Dictionary<string, object>>(@"{""Key1"":1}");
+                IDictionary<string, object> obj = JsonSerializer.Parse<IDictionary<string, object>>(@"{""Key1"":1}");
                 Assert.Equal(1, obj.Count);
                 JsonElement element = (JsonElement)obj["Key1"];
                 Assert.Equal(JsonValueType.Number, element.Type);
@@ -180,6 +181,42 @@ namespace System.Text.Json.Serialization.Tests
             }
         }
 
+        [Fact]
+        public static void ImplementsIDictionaryOfObject()
+        {
+            var input = new StringToObjectIDictionaryWrapper(new Dictionary<string, object>
+            {
+                { "Name", "David" },
+                { "Age", 32 }
+            });
+
+            string json = JsonSerializer.ToString(input, typeof(IDictionary<string, object>));
+            Assert.Equal(@"{""Name"":""David"",""Age"":32}", json);
+
+            IDictionary<string, object> obj = JsonSerializer.Parse<IDictionary<string, object>>(json);
+            Assert.Equal(2, obj.Count);
+            Assert.Equal("David", ((JsonElement)obj["Name"]).GetString());
+            Assert.Equal(32, ((JsonElement)obj["Age"]).GetInt32());
+        }
+
+        [Fact]
+        public static void ImplementsIDictionaryOfString()
+        {
+            var input = new StringToStringIDictionaryWrapper(new Dictionary<string, string>
+            {
+                { "Name", "David" },
+                { "Job", "Software Architect" }
+            });
+
+            string json = JsonSerializer.ToString(input, typeof(IDictionary<string, string>));
+            Assert.Equal(@"{""Name"":""David"",""Job"":""Software Architect""}", json);
+
+            IDictionary<string, string> obj = JsonSerializer.Parse<IDictionary<string, string>>(json);
+            Assert.Equal(2, obj.Count);
+            Assert.Equal("David", obj["Name"]);
+            Assert.Equal("Software Architect", obj["Job"]);
+        }
+
         [Theory]
         [InlineData(typeof(ImmutableDictionary<string, string>), "\"headers\"")]
         [InlineData(typeof(Dictionary<string, string>), "\"headers\"")]
@@ -790,7 +827,7 @@ namespace System.Text.Json.Serialization.Tests
         public static void DeserializeUserDefinedDictionaryThrows()
         {
             string json = @"{""Hello"":1,""Hello2"":2}";
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<UserDefinedImmutableDictionary>(json));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<IImmutableDictionaryWrapper>(json));
         }
 
         public class ClassWithDictionaryButNoSetter
@@ -807,81 +844,5 @@ namespace System.Text.Json.Serialization.Tests
         {
             [JsonIgnore] public Dictionary<int, int> MyDictionary { get; set; }
         }
-
-        public class UserDefinedImmutableDictionary : IImmutableDictionary<string, int>
-        {
-            public int this[string key] => throw new NotImplementedException();
-
-            public IEnumerable<string> Keys => throw new NotImplementedException();
-
-            public IEnumerable<int> Values => throw new NotImplementedException();
-
-            public int Count => throw new NotImplementedException();
-
-            public IImmutableDictionary<string, int> Add(string key, int value)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> AddRange(IEnumerable<KeyValuePair<string, int>> pairs)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> Clear()
-            {
-                throw new NotImplementedException();
-            }
-
-            public bool Contains(KeyValuePair<string, int> pair)
-            {
-                throw new NotImplementedException();
-            }
-
-            public bool ContainsKey(string key)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> Remove(string key)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> RemoveRange(IEnumerable<string> keys)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> SetItem(string key, int value)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IImmutableDictionary<string, int> SetItems(IEnumerable<KeyValuePair<string, int>> items)
-            {
-                throw new NotImplementedException();
-            }
-
-            public bool TryGetKey(string equalKey, out string actualKey)
-            {
-                throw new NotImplementedException();
-            }
-
-            public bool TryGetValue(string key, out int value)
-            {
-                throw new NotImplementedException();
-            }
-
-            IEnumerator IEnumerable.GetEnumerator()
-            {
-                throw new NotImplementedException();
-            }
-        }
     }
 }
diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Dictionary.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Dictionary.cs
new file mode 100644 (file)
index 0000000..a18efbb
--- /dev/null
@@ -0,0 +1,236 @@
+// 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;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public class IImmutableDictionaryWrapper : IImmutableDictionary<string, int>
+    {
+        public int this[string key] => throw new NotImplementedException();
+
+        public IEnumerable<string> Keys => throw new NotImplementedException();
+
+        public IEnumerable<int> Values => throw new NotImplementedException();
+
+        public int Count => throw new NotImplementedException();
+
+        public IImmutableDictionary<string, int> Add(string key, int value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> AddRange(IEnumerable<KeyValuePair<string, int>> pairs)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> Clear()
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool Contains(KeyValuePair<string, int> pair)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool ContainsKey(string key)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> Remove(string key)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> RemoveRange(IEnumerable<string> keys)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> SetItem(string key, int value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IImmutableDictionary<string, int> SetItems(IEnumerable<KeyValuePair<string, int>> items)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool TryGetKey(string equalKey, out string actualKey)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool TryGetValue(string key, out int value)
+        {
+            throw new NotImplementedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class StringToObjectIDictionaryWrapper : IDictionary<string, object>
+    {
+        private readonly IDictionary<string, object> _dict;
+
+        public StringToObjectIDictionaryWrapper(IDictionary<string, object> dict)
+        {
+            _dict = dict;
+        }
+
+        public object this[string key] { get => _dict[key]; set => _dict[key] = value; }
+
+        public ICollection<string> Keys => _dict.Keys;
+
+        public ICollection<object> Values => _dict.Values;
+
+        public int Count => _dict.Count;
+
+        public bool IsReadOnly => false;
+
+        public void Add(string key, object value)
+        {
+            _dict.Add(key, value);
+        }
+
+        public void Add(KeyValuePair<string, object> item)
+        {
+            _dict.Add(item.Key, item.Value);
+        }
+
+        public void Clear()
+        {
+            _dict.Clear();
+        }
+
+        public bool Contains(KeyValuePair<string, object> item)
+        {
+            return _dict.Contains(item);
+        }
+
+        public bool ContainsKey(string key)
+        {
+            return _dict.ContainsKey(key);
+        }
+
+        public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
+        {
+            _dict.CopyTo(array, arrayIndex);
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            return _dict.GetEnumerator();
+        }
+
+        public bool Remove(string key)
+        {
+            return _dict.Remove(key);
+        }
+
+        public bool Remove(KeyValuePair<string, object> item)
+        {
+            return _dict.Remove(item);
+        }
+
+        public bool TryGetValue(string key, out object value)
+        {
+            return _dict.TryGetValue(key, out value);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+
+    public class StringToStringIDictionaryWrapper : IDictionary<string, string>
+    {
+        private readonly IDictionary<string, string> _dict;
+
+        public StringToStringIDictionaryWrapper(IDictionary<string, string> dict)
+        {
+            _dict = dict;
+        }
+
+        public string this[string key] { get => _dict[key]; set => _dict[key] = value; }
+
+        public ICollection<string> Keys => _dict.Keys;
+
+        public ICollection<string> Values => _dict.Values;
+
+        public int Count => _dict.Count;
+
+        public bool IsReadOnly => false;
+
+        public void Add(string key, string value)
+        {
+            _dict.Add(key, value);
+        }
+
+        public void Add(KeyValuePair<string, string> item)
+        {
+            _dict.Add(item.Key, item.Value);
+        }
+
+        public void Clear()
+        {
+            _dict.Clear();
+        }
+
+        public bool Contains(KeyValuePair<string, string> item)
+        {
+            return _dict.Contains(item);
+        }
+
+        public bool ContainsKey(string key)
+        {
+            return _dict.ContainsKey(key);
+        }
+
+        public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
+        {
+            _dict.CopyTo(array, arrayIndex);
+        }
+
+        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
+        {
+            return _dict.GetEnumerator();
+        }
+
+        public bool Remove(string key)
+        {
+            return _dict.Remove(key);
+        }
+
+        public bool Remove(KeyValuePair<string, string> item)
+        {
+            return _dict.Remove(item);
+        }
+
+        public bool TryGetValue(string key, out string value)
+        {
+            return _dict.TryGetValue(key, out value);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}
index 089f72c..ad1f641 100644 (file)
@@ -53,6 +53,7 @@
     <Compile Include="Serialization\Stream.ReadTests.cs" />
     <Compile Include="Serialization\Stream.WriteTests.cs" />
     <Compile Include="Serialization\TestClasses.cs" />
+    <Compile Include="Serialization\TestClasses.Dictionary.cs" />
     <Compile Include="Serialization\TestClasses.Polymorphic.cs" />
     <Compile Include="Serialization\TestClasses.SimpleTestClass.cs" />
     <Compile Include="Serialization\TestClasses.SimpleTestClassWithNullables.cs" />