Guard against NRE when deserializing extension data into dictionary without parameter...
authorAlan Isaac <alan.pinkert@gmail.com>
Sat, 29 Feb 2020 14:44:35 +0000 (09:44 -0500)
committerGitHub <noreply@github.com>
Sat, 29 Feb 2020 14:44:35 +0000 (06:44 -0800)
* changed exception behavior for immutable dictionary

* refactored tests for deserialize

* using full generic dictionary for tests

* moved where exception is thrown

* added immutable serialization test

* added custom immutable dictionary extension data test

* refactored private ctor dictionary to inherit

* removed unused static create

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs

index 7ecdf4c..10302df 100644 (file)
@@ -117,7 +117,11 @@ namespace System.Text.Json
                     jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments()[1].UnderlyingSystemType == typeof(object) ||
                     jsonPropertyInfo.DeclaredPropertyType.GetGenericArguments()[1].UnderlyingSystemType == typeof(JsonElement));
 
-                Debug.Assert(jsonPropertyInfo.RuntimeClassInfo.CreateObject != null);
+                if (jsonPropertyInfo.RuntimeClassInfo.CreateObject == null)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(jsonPropertyInfo.DeclaredPropertyType);
+                }
+
                 extensionData = (IDictionary?)jsonPropertyInfo.RuntimeClassInfo.CreateObject();
                 jsonPropertyInfo.SetValueAsObject(obj, extensionData);
             }
index b23ef61..34397f5 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Linq;
 using Xunit;
@@ -692,6 +693,85 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void DeserializeIntoImmutableDictionaryProperty()
+        {
+            // baseline
+            JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>(@"{}");
+            JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>(@"{}");
+            JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>(@"{}");
+            JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>(@"{}");
+
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutable>("{\"hello\":\"world\"}"));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyAsImmutableJsonElement>("{\"hello\":\"world\"}"));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructor>("{\"hello\":\"world\"}"));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyPrivateConstructorJsonElement>("{\"hello\":\"world\"}"));
+            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutable>("{\"hello\":\"world\"}"));
+            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithExtensionPropertyCustomIImmutableJsonElement>("{\"hello\":\"world\"}"));
+        }
+
+        [Fact]
+        public static void SerializeIntoImmutableDictionaryProperty()
+        {
+            // attempt to serialize a null immutable dictionary
+            string expectedJson = "{}";
+            var obj = new ClassWithExtensionPropertyAsImmutable();
+            var json = JsonSerializer.Serialize(obj);
+            Assert.Equal(expectedJson, json);
+
+            // attempt to serialize an empty immutable dictionary
+            expectedJson = "{}";
+            obj = new ClassWithExtensionPropertyAsImmutable();
+            obj.MyOverflow = ImmutableDictionary<string, object>.Empty;
+            json = JsonSerializer.Serialize(obj);
+            Assert.Equal(expectedJson, json);
+
+            // attempt to serialize a populated immutable dictionary
+            expectedJson = "{\"hello\":\"world\"}";
+            obj = new ClassWithExtensionPropertyAsImmutable();
+            var dictionaryStringObject = new Dictionary<string, object> { { "hello", "world" } };
+            obj.MyOverflow = ImmutableDictionary.CreateRange(dictionaryStringObject);
+            json = JsonSerializer.Serialize(obj);
+            Assert.Equal(expectedJson, json);
+        }
+
+        private class ClassWithExtensionPropertyAsImmutable
+        {
+            [JsonExtensionData]
+            public ImmutableDictionary<string, object> MyOverflow { get; set; }
+        }
+
+        private class ClassWithExtensionPropertyAsImmutableJsonElement
+        {
+            [JsonExtensionData]
+            public ImmutableDictionary<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        private class ClassWithExtensionPropertyPrivateConstructor
+        {
+            [JsonExtensionData]
+            public GenericIDictionaryWrapperPrivateConstructor<string, object> MyOverflow { get; set; }
+        }
+
+        private class ClassWithExtensionPropertyPrivateConstructorJsonElement
+        {
+            [JsonExtensionData]
+            public GenericIDictionaryWrapperPrivateConstructor<string, JsonElement> MyOverflow { get; set; }
+        }
+
+        private class ClassWithExtensionPropertyCustomIImmutable
+        {
+            [JsonExtensionData]
+            public GenericIImmutableDictionaryWrapper<string, object> MyOverflow { get; set; }
+        }
+
+        private class ClassWithExtensionPropertyCustomIImmutableJsonElement
+        {
+            [JsonExtensionData]
+            public GenericIImmutableDictionaryWrapper<string, JsonElement> MyOverflow { get; set; }
+        }
+
+
+        [Fact]
         public static void CustomObjectConverterInExtensionProperty()
         {
             const string Json = "{\"hello\": \"world\"}";
index b939d80..a83a10b 100644 (file)
@@ -861,6 +861,11 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class GenericIDictionaryWrapperPrivateConstructor<TKey, TValue> : GenericIDictionaryWrapper<TKey, TValue>
+    {
+        private GenericIDictionaryWrapperPrivateConstructor() { }
+    }
+
     public class ReadOnlyStringToStringIDictionaryWrapper : StringToStringIDictionaryWrapper
     {
         public override bool IsReadOnly => true;
index 95bc12b..e28c63d 100644 (file)
@@ -219,6 +219,91 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class GenericIImmutableDictionaryWrapper<TKey, TValue> : IImmutableDictionary<TKey, TValue>
+    {
+        private ImmutableDictionary<TKey, TValue> _dictionary;
+
+        public GenericIImmutableDictionaryWrapper() { }
+
+        public GenericIImmutableDictionaryWrapper(Dictionary<TKey, TValue> items)
+        {
+            _dictionary = ImmutableDictionary.CreateRange(items);
+        }
+
+        public TValue this[TKey key] => _dictionary[key];
+
+        public IEnumerable<TKey> Keys => _dictionary.Keys;
+
+        public IEnumerable<TValue> Values => _dictionary.Values;
+
+        public int Count => _dictionary.Count;
+
+        public IImmutableDictionary<TKey, TValue> Add(TKey key, TValue value)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).Add(key, value);
+        }
+
+        public IImmutableDictionary<TKey, TValue> AddRange(IEnumerable<KeyValuePair<TKey, TValue>> pairs)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).AddRange(pairs);
+        }
+
+        public IImmutableDictionary<TKey, TValue> Clear()
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).Clear();
+        }
+
+        public bool Contains(KeyValuePair<TKey, TValue> pair)
+        {
+            return _dictionary.Contains(pair);
+        }
+
+        public bool ContainsKey(TKey key)
+        {
+            return _dictionary.ContainsKey(key);
+        }
+
+        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).GetEnumerator();
+        }
+
+        public IImmutableDictionary<TKey, TValue> Remove(TKey key)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).Remove(key);
+        }
+
+        public IImmutableDictionary<TKey, TValue> RemoveRange(IEnumerable<TKey> keys)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).RemoveRange(keys);
+        }
+
+        public IImmutableDictionary<TKey, TValue> SetItem(TKey key, TValue value)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).SetItem(key, value);
+        }
+
+        public IImmutableDictionary<TKey, TValue> SetItems(IEnumerable<KeyValuePair<TKey, TValue>> items)
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).SetItems(items);
+        }
+
+        public bool TryGetKey(TKey equalKey, out TKey actualKey)
+        {
+            return _dictionary.TryGetKey(equalKey, out actualKey);
+        }
+
+        public bool TryGetValue(TKey key, out TValue value)
+        {
+            return _dictionary.TryGetValue(key, out value);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return ((IImmutableDictionary<TKey, TValue>)_dictionary).GetEnumerator();
+        }
+    }
+
     public class StringIImmutableListWrapper : IImmutableList<string>
     {
         private ImmutableList<string> _list = ImmutableList.Create<string>();