Add support for more collections (dotnet/corefx#38319)
authorLayomi Akinrinade <laakinri@microsoft.com>
Sat, 15 Jun 2019 23:49:03 +0000 (19:49 -0400)
committerGitHub <noreply@github.com>
Sat, 15 Jun 2019 23:49:03 +0000 (19:49 -0400)
* Add support for ISet

* Add support for IEnumerable, IList, and ICollection (non-generic)

* Add support for non-generic IDictionary

* Address review feedback

* Remove unused exception resource and remove ISet<ISet<T>> (de)serialization limitation

* Add support for non-generic collections that have a constructor that takes an IEnumerable or IDictionary

* Condense branches for types we need to set runtime types for

*  Add support for non-generic collections that have a constructor that takes an IList

* Add support for Hashtable and SortedList

* Verify sorted dictionary works

* Add support for KeyValuePair

* Create concrete instance of types Foo where possible

* Use generic constructor for KeyValuePair

* Fix KeyValuePair creation in tests

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

48 files changed:
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIEnumerableConstructibleConverter.cs with 77% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableConverter.cs with 53% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.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/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestStruct.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs
src/libraries/System.Text.Json/tests/Serialization/TestData.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 9e06826..fab63f0 100644 (file)
   <data name="DeserializeDuplicateKey" xml:space="preserve">
     <value>An item with the same property name '{0}' has already been added.</value>
   </data>
-  <data name="DeserializeTypeNotSupported" xml:space="preserve">
-    <value>Deserialization of type {0} is not supported.</value>
-  </data>
   <data name="SerializationDataExtensionPropertyInvalid" xml:space="preserve">
     <value>The data extension property '{0}.{1}' does not match the required signature of IDictionary&lt;string, JsonElement&gt; or IDictionary&lt;string, object&gt;.</value>
   </data>
index 6010ed1..44c9fde 100644 (file)
     <Compile Include="System\Text\Json\Serialization\Converters\DefaultArrayConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\DefaultConverters.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\DefaultConvertersOfTValue.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\DefaultEnumerableConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\DefaultIEnumerableConstructibleConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\DefaultImmutableConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultICollectionConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultIDictionaryConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultImmutableEnumerableConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultImmutableDictionaryConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterBoolean.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterByte.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterByteArray.cs" />
@@ -80,6 +81,7 @@
     <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamePolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonDictionaryConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonEnumerableConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonExtensionDataAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
index 334e86b..1554688 100644 (file)
@@ -9,11 +9,22 @@ namespace System.Text.Json
     /// </summary>
     internal enum ClassType
     {
-        Unknown = 0,        // typeof(object)
-        Object = 1,         // POCO or rich data type
-        Value = 2,          // Data type with single value
-        Enumerable = 3,     // IEnumerable
-        Dictionary = 4,     // IDictionary
-        ImmutableDictionary = 5, // Immutable Dictionary
+        // typeof(object)
+        Unknown = 0,
+        // POCO or rich data type
+        Object = 1,
+        // Data type with single value
+        Value = 2,
+        // IEnumerable
+        Enumerable = 3,
+        // IDictionary
+        Dictionary = 4,
+        // Is deserialized by passing a IDictionary to its constructor
+        // i.e. immutable dictionaries, Hashtable, SortedList,
+        IDictionaryConstructible = 5,
+        // KeyValuePair
+        // TODO: Utilize converter mechanism to handle (de)serialization of KeyValuePair
+        // when it goes through: https://github.com/dotnet/corefx/issues/36639.
+        KeyValuePair = 6,
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs
deleted file mode 100644 (file)
index 3e8c81f..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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.Text.Json.Serialization.Policies;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    internal class JsonEnumerableT<T> : ICollection<T>, IEnumerable<T>, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>
-    {
-        List<T> _list;
-
-        public JsonEnumerableT(IList sourceList)
-        {
-            // TODO: Change sourceList from IList to List<T> so we can do a direct assignment here.
-            _list = new List<T>();
-
-            foreach (object item in sourceList)
-            {
-                _list.Add((T)item);
-            }
-        }
-
-        public T this[int index] { get => (T)_list[index]; set => _list[index] = value; }
-
-        public int Count => _list.Count;
-
-        public bool IsReadOnly => false;
-
-        public void Add(T item)
-        {
-            _list.Add(item);
-        }
-
-        public void Clear()
-        {
-            _list.Clear();
-        }
-
-        public bool Contains(T item)
-        {
-            return _list.Contains(item);
-        }
-
-        public void CopyTo(T[] array, int arrayIndex)
-        {
-            _list.CopyTo(array, arrayIndex);
-        }
-
-        public IEnumerator<T> GetEnumerator()
-        {
-            return _list.GetEnumerator();
-        }
-
-        public int IndexOf(T item)
-        {
-            return _list.IndexOf(item);
-        }
-
-        public void Insert(int index, T item)
-        {
-            _list.Insert(index, item);
-        }
-
-        public bool Remove(T item)
-        {
-            return _list.Remove(item);
-        }
-
-        public void RemoveAt(int index)
-        {
-            _list.RemoveAt(index);
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
-    }
-
-    internal sealed class DefaultEnumerableConverter : JsonEnumerableConverter
-    {
-        public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
-        {
-            Type elementType = state.Current.GetElementType();
-
-            Type t = typeof(JsonEnumerableT<>).MakeGenericType(elementType);
-            return (IEnumerable)Activator.CreateInstance(t, sourceList);
-        }
-    }
-}
@@ -3,19 +3,18 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
-using System.Collections.Concurrent;
 using System.Text.Json.Serialization.Policies;
 
 namespace System.Text.Json.Serialization.Converters
 {
-    internal sealed class DefaultIEnumerableConstructibleConverter : JsonEnumerableConverter
+    internal sealed class DefaultICollectionConverter : JsonEnumerableConverter
     {
         public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
         {
             Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
             JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
             JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
-            return propertyInfo.CreateIEnumerableConstructibleType(enumerableType, sourceList);
+            return propertyInfo.CreateIEnumerableInstance(enumerableType, sourceList, state.JsonPath, options);
         }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs
new file mode 100644 (file)
index 0000000..351a019
--- /dev/null
@@ -0,0 +1,20 @@
+// 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.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter
+    {
+        public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
+        {
+            Type dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+            JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
+            return propertyInfo.CreateIDictionaryInstance(dictionaryType, sourceDictionary, state.JsonPath, options);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs
new file mode 100644 (file)
index 0000000..95fa7bd
--- /dev/null
@@ -0,0 +1,69 @@
+// 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.Diagnostics;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class DefaultImmutableDictionaryConverter : JsonDictionaryConverter
+    {
+        private const string ImmutableDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableDictionary`2";
+        private const string ImmutableDictionaryGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableDictionary`2";
+        private const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2";
+
+        public static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
+        {
+            // Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
+            string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);
+
+            // Exit if we have registered this immutable dictionary type.
+            if (options.CreateRangeDelegatesContainsKey(delegateKey))
+            {
+                return;
+            }
+
+            // Get the constructing type.
+            Type constructingType = underlyingType.Assembly.GetType(constructingTypeName);
+
+            // Create a delegate which will point to the CreateRange method.
+            object createRangeDelegate;
+            createRangeDelegate = options.ClassMaterializerStrategy.ImmutableDictionaryCreateRange(constructingType, elementType);
+
+            // Cache the delegate
+            options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
+        }
+
+        public static bool IsImmutableDictionary(Type type)
+        {
+            if (!type.IsGenericType)
+            {
+                return false;
+            }
+
+            switch (type.GetGenericTypeDefinition().FullName)
+            {
+                case ImmutableDictionaryGenericTypeName:
+                case ImmutableDictionaryGenericInterfaceTypeName:
+                case ImmutableSortedDictionaryGenericTypeName:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
+        {
+            Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+            Type elementType = state.Current.GetElementType();
+
+            string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _);
+
+            JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
+            return propertyInfo.CreateImmutableDictionaryInstance(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath, options);
+        }
+    }
+}
@@ -3,18 +3,14 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
-using System.Collections.Generic;
-using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Text.Json.Serialization.Policies;
 
 namespace System.Text.Json.Serialization.Converters
 {
     // This converter returns enumerables in the System.Collections.Immutable namespace.
-    internal sealed class DefaultImmutableConverter : JsonEnumerableConverter
+    internal sealed class DefaultImmutableEnumerableConverter : JsonEnumerableConverter
     {
-        public const string ImmutableNamespace = "System.Collections.Immutable";
-
         private const string ImmutableListTypeName = "System.Collections.Immutable.ImmutableList";
         private const string ImmutableListGenericTypeName = "System.Collections.Immutable.ImmutableList`1";
         private const string ImmutableListGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableList`1";
@@ -41,84 +37,58 @@ namespace System.Text.Json.Serialization.Converters
         private const string ImmutableSortedDictionaryTypeName = "System.Collections.Immutable.ImmutableSortedDictionary";
         private const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2";
 
-        internal delegate object ImmutableCreateRangeDelegate<T>(IEnumerable<T> items);
-        internal delegate object ImmutableDictCreateRangeDelegate<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> items);
-
-        private static ConcurrentDictionary<string, object> s_createRangeDelegates = new ConcurrentDictionary<string, object>();
-
-        private static string GetConstructingTypeName(string immutableCollectionTypeName)
+        public static string GetDelegateKey(
+            Type immutableCollectionType,
+            Type elementType,
+            out Type underlyingType,
+            out string constructingTypeName)
         {
-            switch (immutableCollectionTypeName)
+            // Use the generic type definition of the immutable collection to determine an appropriate constructing type,
+            // i.e. a type that we can invoke the `CreateRange<elementType>` method on, which returns an assignable immutable collection.
+            underlyingType = immutableCollectionType.GetGenericTypeDefinition();
+
+            switch (underlyingType.FullName)
             {
                 case ImmutableListGenericTypeName:
                 case ImmutableListGenericInterfaceTypeName:
-                    return ImmutableListTypeName;
+                    constructingTypeName = ImmutableListTypeName;
+                    break;
                 case ImmutableStackGenericTypeName:
                 case ImmutableStackGenericInterfaceTypeName:
-                    return ImmutableStackTypeName;
+                    constructingTypeName = ImmutableStackTypeName;
+                    break;
                 case ImmutableQueueGenericTypeName:
                 case ImmutableQueueGenericInterfaceTypeName:
-                    return ImmutableQueueTypeName;
+                    constructingTypeName = ImmutableQueueTypeName;
+                    break;
                 case ImmutableSortedSetGenericTypeName:
-                    return ImmutableSortedSetTypeName;
+                    constructingTypeName = ImmutableSortedSetTypeName;
+                    break;
                 case ImmutableHashSetGenericTypeName:
                 case ImmutableSetGenericInterfaceTypeName:
-                    return ImmutableHashSetTypeName;
+                    constructingTypeName = ImmutableHashSetTypeName;
+                    break;
                 case ImmutableDictionaryGenericTypeName:
                 case ImmutableDictionaryGenericInterfaceTypeName:
-                    return ImmutableDictionaryTypeName;
+                    constructingTypeName = ImmutableDictionaryTypeName;
+                    break;
                 case ImmutableSortedDictionaryGenericTypeName:
-                    return ImmutableSortedDictionaryTypeName;
+                    constructingTypeName = ImmutableSortedDictionaryTypeName;
+                    break;
                 default:
-                    // TODO: Refactor exception throw following serialization exception changes.
-                    throw new NotSupportedException(SR.Format(SR.DeserializeTypeNotSupported, immutableCollectionTypeName));
+                    throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(immutableCollectionType, null, null);
             }
-        }
-
-        private static string GetDelegateKey(
-            Type immutableCollectionType,
-            Type elementType,
-            out Type underlyingType,
-            out string constructingTypeName)
-        {
-            // Use the generic type definition of the immutable collection to determine an appropriate constructing type,
-            // i.e. a type that we can invoke the `CreateRange<elementType>` method on, which returns an assignable immutable collection.
-            underlyingType = immutableCollectionType.GetGenericTypeDefinition();
-            constructingTypeName = GetConstructingTypeName(underlyingType.FullName);
 
             return $"{constructingTypeName}:{elementType.FullName}";
         }
 
-        internal static bool TypeIsImmutableDictionary(Type type)
-        {
-            if (!type.IsGenericType)
-            {
-                return false;
-            }
-
-            switch (type.GetGenericTypeDefinition().FullName)
-            {
-                case ImmutableDictionaryGenericTypeName:
-                case ImmutableDictionaryGenericInterfaceTypeName:
-                case ImmutableSortedDictionaryGenericTypeName:
-                    return true;
-                default:
-                    return false;
-            }
-        }
-
-        internal static bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate)
-        {
-            return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null;
-        }
-
-        internal static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
+        public static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
         {
             // Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
             string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);
 
             // Exit if we have registered this immutable collection type.
-            if (s_createRangeDelegates.ContainsKey(delegateKey))
+            if (options.CreateRangeDelegatesContainsKey(delegateKey))
             {
                 return;
             }
@@ -131,29 +101,7 @@ namespace System.Text.Json.Serialization.Converters
             createRangeDelegate = options.ClassMaterializerStrategy.ImmutableCollectionCreateRange(constructingType, elementType);
 
             // Cache the delegate
-            s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate);
-        }
-
-        internal static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
-        {
-            // Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
-            string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);
-
-            // Exit if we have registered this immutable collection type.
-            if (s_createRangeDelegates.ContainsKey(delegateKey))
-            {
-                return;
-            }
-
-            // Get the constructing type.
-            Type constructingType = underlyingType.Assembly.GetType(constructingTypeName);
-
-            // Create a delegate which will point to the CreateRange method.
-            object createRangeDelegate;
-            createRangeDelegate = options.ClassMaterializerStrategy.ImmutableDictionaryCreateRange(constructingType, elementType);
-
-            // Cache the delegate
-            s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate);
+            options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
         }
 
         public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
@@ -162,24 +110,10 @@ namespace System.Text.Json.Serialization.Converters
             Type elementType = state.Current.GetElementType();
 
             string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
-            Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey));
-
-            JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
-            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
-            return propertyInfo.CreateImmutableCollectionFromList(immutableCollectionType, delegateKey, sourceList, state.JsonPath);
-        }
-
-        internal IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
-        {
-            Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            Type elementType = state.Current.GetElementType();
-
-            string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
-            Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey));
 
             JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
             JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
-            return propertyInfo.CreateImmutableCollectionFromDictionary(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath);
+            return propertyInfo.CreateImmutableCollectionInstance(immutableCollectionType, delegateKey, sourceList, state.JsonPath, options);
         }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs
new file mode 100644 (file)
index 0000000..720562f
--- /dev/null
@@ -0,0 +1,18 @@
+// 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.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter
+    {
+        public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
+        {
+            Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+            return (IDictionary)Activator.CreateInstance(enumerableType, sourceDictionary);
+        }
+    }
+}
index 7244fa5..3c535a4 100644 (file)
@@ -2,6 +2,7 @@
 // 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.Reflection;
 using System.Text.Json.Serialization;
 
@@ -29,7 +30,7 @@ namespace System.Text.Json
             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)
+                if (jsonInfo.ElementClassInfo.ClassType != ClassType.Unknown || propertyType == typeof(IDictionary))
                 {
                     Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetDictionaryConcreteType();
                     if (propertyType != newPropertyType)
@@ -38,6 +39,21 @@ namespace System.Text.Json
                     }
                 }
             }
+            else if (jsonInfo.ClassType == ClassType.Enumerable &&
+                !propertyType.IsArray &&
+                (IsDeserializedByAssigningFromList(propertyType) || IsSetInterface(propertyType)))
+            {
+                JsonClassInfo elementClassInfo = jsonInfo.ElementClassInfo;
+                JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options);
+
+                // Get a runtime type for the property. e.g. ISet<T> -> HashSet<T>, ICollection -> List<object>
+                // We use the element's JsonPropertyInfo so we can utilize the generic support.
+                Type newPropertyType = elementPropertyInfo.GetConcreteType(propertyType);
+                if ((propertyType != newPropertyType) && propertyType.IsAssignableFrom(newPropertyType))
+                {
+                    jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options);
+                }
+            }
 
             if (propertyInfo != null)
             {
@@ -65,7 +81,8 @@ namespace System.Text.Json
             {
                 case ClassType.Enumerable:
                 case ClassType.Dictionary:
-                case ClassType.ImmutableDictionary:
+                case ClassType.IDictionaryConstructible:
+                case ClassType.KeyValuePair:
                 case ClassType.Unknown:
                     collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo);
                     break;
index 7e63958..664ca8a 100644 (file)
@@ -94,35 +94,37 @@ namespace System.Text.Json
             switch (ClassType)
             {
                 case ClassType.Object:
-                    var propertyNames = new HashSet<string>(StringComparer.Ordinal);
-
-                    foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                     {
-                        // Ignore indexers
-                        if (propertyInfo.GetIndexParameters().Length > 0)
-                        {
-                            continue;
-                        }
+                        var propertyNames = new HashSet<string>(StringComparer.Ordinal);
 
-                        // For now we only support public getters\setters
-                        if (propertyInfo.GetMethod?.IsPublic == true ||
-                            propertyInfo.SetMethod?.IsPublic == true)
+                        foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
                         {
-                            JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
-
-                            Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
-
-                            // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
-                            if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString))
+                            // Ignore indexers
+                            if (propertyInfo.GetIndexParameters().Length > 0)
                             {
-                                ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
+                                continue;
                             }
 
-                            jsonPropertyInfo.ClearUnusedValuesAfterAdd();
+                            // For now we only support public getters\setters
+                            if (propertyInfo.GetMethod?.IsPublic == true ||
+                                propertyInfo.SetMethod?.IsPublic == true)
+                            {
+                                JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
+
+                                Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
+
+                                // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
+                                if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString))
+                                {
+                                    ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
+                                }
+
+                                jsonPropertyInfo.ClearUnusedValuesAfterAdd();
+                            }
                         }
-                    }
 
-                    DetermineExtensionDataProperty();
+                        DetermineExtensionDataProperty();
+                    }
                     break;
                 case ClassType.Enumerable:
                 case ClassType.Dictionary:
@@ -138,7 +140,7 @@ namespace System.Text.Json
                         ElementClassInfo = options.GetOrAddClass(elementType);
                     }
                     break;
-                case ClassType.ImmutableDictionary:
+                case ClassType.IDictionaryConstructible:
                     {
                         // Add a single property that maps to the class type so we can have policies applied.
                         AddPolicyProperty(type, options);
@@ -152,6 +154,39 @@ namespace System.Text.Json
                         ElementClassInfo = options.GetOrAddClass(elementType);
                     }
                     break;
+                // TODO: Utilize converter mechanism to handle (de)serialization of KeyValuePair
+                // when it goes through: https://github.com/dotnet/corefx/issues/36639.
+                case ClassType.KeyValuePair:
+                    {
+                        // For deserialization, we treat it as ClassType.IDictionaryConstructible so we can parse it like a dictionary
+                        // before using converter-like logic to create a KeyValuePair instance.
+
+                        // Add a single property that maps to the class type so we can have policies applied.
+                        AddPolicyProperty(type, options);
+
+                        Type elementType = GetElementType(type, parentType: null, memberInfo: null);
+
+                        // Make this Dictionary<string, object> to accomodate input of form {"Key": "MyKey", "Value": 1}.
+                        CreateObject = options.ClassMaterializerStrategy.CreateConstructor(typeof(Dictionary<string, object>));
+
+                        // Create a ClassInfo that maps to the element type which is used for deserialization and policies.
+                        ElementClassInfo = options.GetOrAddClass(elementType);
+
+                        // For serialization, we treat it like ClassType.Object to utilize the public getters.
+
+                        // Add Key property
+                        PropertyInfo propertyInfo = type.GetProperty("Key", BindingFlags.Public | BindingFlags.Instance);
+                        JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
+                        Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
+                        jsonPropertyInfo.ClearUnusedValuesAfterAdd();
+
+                        // Add Value property.
+                        propertyInfo = type.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
+                        jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
+                        Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
+                        jsonPropertyInfo.ClearUnusedValuesAfterAdd();
+                    }
+                    break;
                 case ClassType.Value:
                 case ClassType.Unknown:
                     // Add a single property that maps to the class type so we can have policies applied.
@@ -289,6 +324,13 @@ namespace System.Text.Json
             return _propertyRefs[0].Info;
         }
 
+        internal JsonPropertyInfo GetPolicyPropertyOfKeyValuePair()
+        {
+            // We have 3 here. One for the KeyValuePair itself, one for Key property, and one for the Value property.
+            Debug.Assert(_propertyRefs.Count == 3);
+            return _propertyRefs[0].Info;
+        }
+
         internal JsonPropertyInfo GetProperty(int index)
         {
             Debug.Assert(index < _propertyRefs.Count);
@@ -374,10 +416,10 @@ namespace System.Text.Json
             return key;
         }
 
-        // Return the element type of the IEnumerable, or return null if not an IEnumerable.
+        // Return the element type of the IEnumerable or KeyValuePair, or return null if not an IEnumerable or KayValuePair.
         public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo)
         {
-            if (!typeof(IEnumerable).IsAssignableFrom(propertyType))
+            if (!typeof(IEnumerable).IsAssignableFrom(propertyType) && !IsKeyValuePair(propertyType))
             {
                 return null;
             }
@@ -395,7 +437,7 @@ namespace System.Text.Json
                 Type[] args = propertyType.GetGenericArguments();
                 ClassType classType = GetClassType(propertyType);
 
-                if ((classType == ClassType.Dictionary || classType == ClassType.ImmutableDictionary) &&
+                if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible || classType == ClassType.KeyValuePair) &&
                     args.Length >= 2 && // It is >= 2 in case there is a IDictionary<TKey, TValue, TSomeExtension>.
                     args[0].UnderlyingSystemType == typeof(string))
                 {
@@ -408,10 +450,18 @@ namespace System.Text.Json
                 }
             }
 
+            if (propertyType.IsAssignableFrom(typeof(IList)) ||
+                propertyType.IsAssignableFrom(typeof(IDictionary)) ||
+                IsDeserializedByConstructingWithIList(propertyType) ||
+                IsDeserializedByConstructingWithIDictionary(propertyType))
+            {
+                return typeof(object);
+            }
+
             throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
         }
 
-        internal static ClassType GetClassType(Type type)
+        public static ClassType GetClassType(Type type)
         {
             Debug.Assert(type != null);
 
@@ -425,18 +475,24 @@ namespace System.Text.Json
                 return ClassType.Value;
             }
 
-            if (DefaultImmutableConverter.TypeIsImmutableDictionary(type))
+            if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(type) ||
+                IsDeserializedByConstructingWithIDictionary(type))
             {
-                return ClassType.ImmutableDictionary;
+                return ClassType.IDictionaryConstructible;
             }
 
-            if (typeof(IDictionary).IsAssignableFrom(type) || 
+            if (typeof(IDictionary).IsAssignableFrom(type) ||
                 (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
                 type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))))
             {
                 return ClassType.Dictionary;
             }
 
+            if (IsKeyValuePair(type))
+            {
+                return ClassType.KeyValuePair;
+            }
+
             if (typeof(IEnumerable).IsAssignableFrom(type))
             {
                 return ClassType.Enumerable;
@@ -449,5 +505,98 @@ namespace System.Text.Json
 
             return ClassType.Object;
         }
+
+        public const string ImmutableNamespaceName = "System.Collections.Immutable";
+
+        private const string EnumerableGenericInterfaceTypeName = "System.Collections.Generic.IEnumerable`1";
+        private const string EnumerableInterfaceTypeName = "System.Collections.IEnumerable";
+
+        private const string ListGenericInterfaceTypeName = "System.Collections.Generic.IList`1";
+        private const string ListInterfaceTypeName = "System.Collections.IList";
+
+        private const string CollectionGenericInterfaceTypeName = "System.Collections.Generic.ICollection`1";
+        private const string CollectionInterfaceTypeName = "System.Collections.ICollection";
+
+        private const string ReadOnlyListGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyList`1";
+
+        private const string ReadOnlyCollectionGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyCollection`1";
+
+        public const string HashtableTypeName = "System.Collections.Hashtable";
+        public const string SortedListTypeName = "System.Collections.SortedList";
+
+        public const string StackTypeName = "System.Collections.Stack";
+        public const string QueueTypeName = "System.Collections.Queue";
+        public const string ArrayListTypeName = "System.Collections.ArrayList";
+
+        public static bool IsDeserializedByAssigningFromList(Type type)
+        {
+            if (type.IsGenericType)
+            {
+                switch (type.GetGenericTypeDefinition().FullName)
+                {
+                    case EnumerableGenericInterfaceTypeName:
+                    case ListGenericInterfaceTypeName:
+                    case CollectionGenericInterfaceTypeName:
+                    case ReadOnlyListGenericInterfaceTypeName:
+                    case ReadOnlyCollectionGenericInterfaceTypeName:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+            else
+            {
+                switch (type.FullName)
+                {
+                    case EnumerableInterfaceTypeName:
+                    case ListInterfaceTypeName:
+                    case CollectionInterfaceTypeName:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        }
+
+        public static bool IsSetInterface(Type type)
+        {
+            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ISet<>);
+        }
+
+        public static bool HasConstructorThatTakesGenericIEnumerable(Type type)
+        {
+            Type elementType = GetElementType(type, parentType: null, memberInfo: null);
+            return type.GetConstructor(new Type[] { typeof(List<>).MakeGenericType(elementType) }) != null;
+        }
+
+        public static bool IsDeserializedByConstructingWithIList(Type type)
+        {
+            switch (type.FullName)
+            {
+                case StackTypeName:
+                case QueueTypeName:
+                case ArrayListTypeName:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        public static bool IsDeserializedByConstructingWithIDictionary(Type type)
+        {
+            switch (type.FullName)
+            {
+                case HashtableTypeName:
+                case SortedListTypeName:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        public static bool IsKeyValuePair(Type type)
+        {
+            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs
new file mode 100644 (file)
index 0000000..98884a3
--- /dev/null
@@ -0,0 +1,13 @@
+// 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;
+
+namespace System.Text.Json.Serialization.Policies
+{
+    internal abstract class JsonDictionaryConverter
+    {
+        public abstract IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options);
+    }
+}
index 3d2d587..fa18769 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
-using System.Collections.Generic;
 
 namespace System.Text.Json.Serialization.Policies
 {
index 0f34a21..c190a61 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using System.Text.Json.Serialization;
@@ -17,9 +16,11 @@ namespace System.Text.Json
     {
         // Cache the converters so they don't get created for every enumerable property.
         private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
-        private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter();
-        private static readonly JsonEnumerableConverter s_jsonIEnumerableConstuctibleConverter = new DefaultIEnumerableConstructibleConverter();
-        private static readonly JsonEnumerableConverter s_jsonImmutableConverter = new DefaultImmutableConverter();
+        private static readonly JsonEnumerableConverter s_jsonICollectionConverter = new DefaultICollectionConverter();
+        private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter();
+        private static readonly JsonEnumerableConverter s_jsonImmutableEnumerableConverter = new DefaultImmutableEnumerableConverter();
+        private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter();
+
 
         private JsonClassInfo _runtimeClassInfo;
 
@@ -77,7 +78,10 @@ namespace System.Text.Json
             {
                 if (_elementClassInfo == null && _elementType != null)
                 {
-                    Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary || ClassType == ClassType.ImmutableDictionary);
+                    Debug.Assert(ClassType == ClassType.Enumerable ||
+                        ClassType == ClassType.Dictionary ||
+                        ClassType == ClassType.IDictionaryConstructible ||
+                        ClassType == ClassType.KeyValuePair);
                     _elementClassInfo = Options.GetOrAddClass(_elementType);
                 }
 
@@ -105,7 +109,9 @@ namespace System.Text.Json
         }
 
         public bool CanBeNull { get; private set; }
+
         public JsonEnumerableConverter EnumerableConverter { get; private set; }
+        public JsonDictionaryConverter DictionaryConverter { get; private set; }
 
         public bool IsNullableType { get; private set; }
 
@@ -180,7 +186,10 @@ namespace System.Text.Json
 
         private void DetermineSerializationCapabilities()
         {
-            if (ClassType != ClassType.Enumerable && ClassType != ClassType.Dictionary && ClassType != ClassType.ImmutableDictionary)
+            if (ClassType != ClassType.Enumerable &&
+                ClassType != ClassType.Dictionary &&
+                ClassType != ClassType.IDictionaryConstructible &&
+                ClassType != ClassType.KeyValuePair)
             {
                 // We serialize if there is a getter + not ignoring readonly properties.
                 ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties);
@@ -215,36 +224,34 @@ namespace System.Text.Json
                     {
                         EnumerableConverter = s_jsonArrayConverter;
                     }
-                    else if (ClassType == ClassType.ImmutableDictionary)
+                    else if (ClassType == ClassType.IDictionaryConstructible)
                     {
-                        DefaultImmutableConverter.RegisterImmutableDictionary(
-                            RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, parentType: null, memberInfo: null), Options);
-                        EnumerableConverter = s_jsonImmutableConverter;
+                        if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName))
+                        {
+                            DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(
+                            RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo), Options);
+                            DictionaryConverter = s_jsonImmutableDictionaryConverter;
+                        }
+                        else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType))
+                        {
+                            DictionaryConverter = s_jsonIDictionaryConverter;
+                        }
                     }
                     else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
                     {
-                        Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo);
-
-                        // If the property type only has interface(s) exposed by JsonEnumerableT<T> then use JsonEnumerableT as the converter.
-                        if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
-                        {
-                            EnumerableConverter = s_jsonEnumerableConverter;
-                        }
-                        // Else if IList can't be assigned from the property type (we populate and return an IList directly)
-                        // and the type can be constructed with an IEnumerable<T>, then use the
-                        // IEnumerableConstructible converter to create the instance.
-                        else if (!typeof(IList).IsAssignableFrom(RuntimePropertyType) &&
-                            RuntimePropertyType.GetConstructor(new Type[] { typeof(List<>).MakeGenericType(elementType) }) != null)
+                        if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) ||
+                            (!typeof(IList).IsAssignableFrom(RuntimePropertyType) && JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType)))
                         {
-                            EnumerableConverter = s_jsonIEnumerableConstuctibleConverter;
+                            EnumerableConverter = s_jsonICollectionConverter;
                         }
-                        // Else if it's a System.Collections.Immutable type with one generic argument.
                         else if (RuntimePropertyType.IsGenericType &&
-                            RuntimePropertyType.FullName.StartsWith(DefaultImmutableConverter.ImmutableNamespace) &&
+                            RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) &&
                             RuntimePropertyType.GetGenericArguments().Length == 1)
                         {
-                            DefaultImmutableConverter.RegisterImmutableCollection(RuntimePropertyType, elementType, Options);
-                            EnumerableConverter = s_jsonImmutableConverter;
+                            DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType,
+                                JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo),
+                                Options);
+                            EnumerableConverter = s_jsonImmutableEnumerableConverter;
                         }
                     }
                 }
@@ -292,16 +299,22 @@ namespace System.Text.Json
             return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
         }
 
-        public abstract IEnumerable CreateImmutableCollectionFromList(Type collectionType, string delegateKey, IList sourceList, string propertyPath);
+        public abstract IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options);
 
-        public abstract IDictionary CreateImmutableCollectionFromDictionary(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath);
+        public abstract IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options);
 
-        public abstract IEnumerable CreateIEnumerableConstructibleType(Type enumerableType, IList sourceList);
+        public abstract IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string propertyPath, JsonSerializerOptions options);
+
+        public abstract IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath, JsonSerializerOptions options);
 
         public abstract IList CreateConverterList();
 
+        public abstract ValueType CreateKeyValuePairInstance(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options);
+
         public abstract Type GetDictionaryConcreteType();
 
+        public abstract Type GetConcreteType(Type type);
+
         public abstract void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
         public abstract void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
         public abstract void SetValueAsObject(object obj, object value);
index ca5dff0..4ff9654 100644 (file)
@@ -93,44 +93,133 @@ namespace System.Text.Json
             return typeof(Dictionary<string, TRuntimeProperty>);
         }
 
+        public override Type GetConcreteType(Type parentType)
+        {
+            if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType))
+            {
+                return typeof(List<TDeclaredProperty>);
+            }
+            else if (JsonClassInfo.IsSetInterface(parentType))
+            {
+                return typeof(HashSet<TDeclaredProperty>);
+            }
+
+            return parentType;
+        }
+
+        public override IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options)
+        {
+            if (parentType.IsGenericType)
+            {
+                Type genericTypeDefinition = parentType.GetGenericTypeDefinition();
+                IEnumerable<TDeclaredProperty> items = CreateGenericTDeclaredPropertyIEnumerable(sourceList);
+
+                if (genericTypeDefinition == typeof(Stack<>))
+                {
+                    return new Stack<TDeclaredProperty>(items);
+                }
+                else if (genericTypeDefinition == typeof(Queue<>))
+                {
+                    return new Queue<TDeclaredProperty>(items);
+                }
+                else if (genericTypeDefinition == typeof(HashSet<>))
+                {
+                    return new HashSet<TDeclaredProperty>(items);
+                }
+                else if (genericTypeDefinition == typeof(LinkedList<>))
+                {
+                    return new LinkedList<TDeclaredProperty>(items);
+                }
+                else if (genericTypeDefinition == typeof(SortedSet<>))
+                {
+                    return new SortedSet<TDeclaredProperty>(items);
+                }
+
+                return (IEnumerable)Activator.CreateInstance(parentType, items);
+            }
+            else
+            {
+                if (parentType == typeof(ArrayList))
+                {
+                    return new ArrayList(sourceList);
+                }
+                // Stack and Queue go into this condition, unless we add a ref to System.Collections.NonGeneric.
+                else
+                {
+                    return (IEnumerable)Activator.CreateInstance(parentType, sourceList);
+                }
+            }
+        }
+
+        public override IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options)
+        {
+            if (parentType.FullName == JsonClassInfo.HashtableTypeName)
+            {
+                return new Hashtable(sourceDictionary);
+            }
+            // SortedList goes into this condition, unless we add a ref to System.Collections.NonGeneric.
+            else
+            {
+                return (IDictionary)Activator.CreateInstance(parentType, sourceDictionary);
+            }
+        }
+
         // Creates an IEnumerable<TRuntimePropertyType> and populates it with the items in the
         // sourceList argument then uses the delegateKey argument to identify the appropriate cached
         // CreateRange<TRuntimePropertyType> method to create and return the desired immutable collection type.
-        public override IEnumerable CreateImmutableCollectionFromList(Type collectionType, string delegateKey, IList sourceList, string propertyPath)
+        public override IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options)
         {
-            if (!DefaultImmutableConverter.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj))
+            if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj))
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, propertyPath);
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath);
             }
 
-            DefaultImmutableConverter.ImmutableCreateRangeDelegate<TRuntimeProperty> createRangeDelegate = (
-                (DefaultImmutableConverter.ImmutableCreateRangeDelegate<TRuntimeProperty>)createRangeDelegateObj);
+            JsonSerializerOptions.ImmutableCreateRangeDelegate<TRuntimeProperty> createRangeDelegate = (
+                (JsonSerializerOptions.ImmutableCreateRangeDelegate<TRuntimeProperty>)createRangeDelegateObj);
 
-            return (IEnumerable)createRangeDelegate.Invoke(CreateGenericIEnumerableFromList(sourceList));
+            return (IEnumerable)createRangeDelegate.Invoke(CreateGenericTRuntimePropertyIEnumerable(sourceList));
         }
 
         // Creates an IEnumerable<TRuntimePropertyType> and populates it with the items in the
         // sourceList argument then uses the delegateKey argument to identify the appropriate cached
         // CreateRange<TRuntimePropertyType> method to create and return the desired immutable collection type.
-        public override IDictionary CreateImmutableCollectionFromDictionary(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath)
+        public override IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options)
         {
-            if (!DefaultImmutableConverter.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj))
+            if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj))
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, propertyPath);
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath);
             }
 
-            DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<string, TRuntimeProperty> createRangeDelegate = (
-                (DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<string, TRuntimeProperty>)createRangeDelegateObj);
+            JsonSerializerOptions.ImmutableDictCreateRangeDelegate<string, TRuntimeProperty> createRangeDelegate = (
+                (JsonSerializerOptions.ImmutableDictCreateRangeDelegate<string, TRuntimeProperty>)createRangeDelegateObj);
 
             return (IDictionary)createRangeDelegate.Invoke(CreateGenericIEnumerableFromDictionary(sourceDictionary));
         }
 
-        public override IEnumerable CreateIEnumerableConstructibleType(Type enumerableType, IList sourceList)
+        public override ValueType CreateKeyValuePairInstance(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
         {
-            return (IEnumerable)Activator.CreateInstance(enumerableType, CreateGenericIEnumerableFromList(sourceList));
+            Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+
+            // Form {"MyKey": 1}.
+            if (sourceDictionary.Count == 1)
+            {
+                IDictionaryEnumerator enumerator = sourceDictionary.GetEnumerator();
+                enumerator.MoveNext();
+                return new KeyValuePair<string, TRuntimeProperty>((string)enumerator.Key, (TRuntimeProperty)enumerator.Value);
+            }
+            // Form {"Key": "MyKey", "Value": 1}.
+            else if (sourceDictionary.Count == 2 &&
+                sourceDictionary["Key"] is string key &&
+                sourceDictionary["Value"] is TRuntimeProperty value
+                )
+            {
+                return new KeyValuePair<string, TRuntimeProperty>(key, value);
+            }
+
+            throw ThrowHelper.GetJsonException_DeserializeUnableToConvertValue(enumerableType, state.JsonPath);
         }
 
-        private IEnumerable<TRuntimeProperty> CreateGenericIEnumerableFromList(IList sourceList)
+        private IEnumerable<TRuntimeProperty> CreateGenericTRuntimePropertyIEnumerable(IList sourceList)
         {
             foreach (object item in sourceList)
             {
@@ -138,6 +227,14 @@ namespace System.Text.Json
             }
         }
 
+        private IEnumerable<TDeclaredProperty> CreateGenericTDeclaredPropertyIEnumerable(IList sourceList)
+        {
+            foreach (object item in sourceList)
+            {
+                yield return (TDeclaredProperty)item;
+            }
+        }
+
         private IEnumerable<KeyValuePair<string, TRuntimeProperty>> CreateGenericIEnumerableFromDictionary(IDictionary sourceDictionary)
         {
             foreach (DictionaryEntry item in sourceDictionary)
index 9914128..bcf6c6a 100644 (file)
@@ -4,6 +4,8 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Text.Json.Serialization.Converters;
+using System.Text.Json.Serialization.Policies;
 
 namespace System.Text.Json
 {
@@ -52,8 +54,7 @@ namespace System.Text.Json
         {
             Debug.Assert(ShouldDeserialize);
 
-            // We need a property in order to a apply a value in a dictionary.
-            if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingImmutableDictionary))
+            if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair))
             {
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath);
                 return;
@@ -68,10 +69,41 @@ namespace System.Text.Json
 
             if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value))
             {
+                if (state.Current.IsProcessingKeyValuePair && state.Current.KeyName == "Key")
+                {
+                    // Handle the special case where the input KeyValuePair is of form {"Key": "MyKey", "Value": 1}
+                    // (as opposed to form {"MyKey": 1}) and the value type is not string.
+                    // If we have one, the ValueConverter failed to read the current token because it should be of type string
+                    // (we only support string keys) but we initially tried to read it as type TRuntimeProperty.
+                    // We have TRuntimeProperty not string because for deserialization, we parse the KeyValuePair as a
+                    // dictionary before creating a KeyValuePair instance in a converter-like manner with the parsed values.
+                    // Because it's dictionary-like parsing, we set the element type of the dictionary earlier on to the KeyValuePair's value
+                    // type, which led us here.
+                    // If there's no ValueConverter, the runtime type of the KeyValuePair's value is probably an object, dictionary or enumerable.
+                    JsonValueConverter<string> stringConverter = DefaultConverters<string>.s_converter;
+                    if (!stringConverter.TryRead(typeof(string), ref reader, out string strValue))
+                    {
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(string), reader, state.JsonPath);
+                    }
+
+                    object objValue = strValue;
+
+                    JsonSerializer.ApplyValueToEnumerable(ref objValue, ref state, ref reader);
+                    return;
+                }
+
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath);
                 return;
             }
 
+            if (state.Current.IsProcessingKeyValuePair)
+            {
+                // The value is being applied to a Dictionary<string, object>, so we need to cast to object here.
+                object objValue = value;
+                JsonSerializer.ApplyValueToEnumerable(ref objValue, ref state, ref reader);
+                return;
+            }
+
             JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader);
         }
 
index 7ee0975..04bd680 100644 (file)
@@ -37,7 +37,7 @@ namespace System.Text.Json
 
             // Verify that we don't have a multidimensional array.
             Type arrayType = jsonPropertyInfo.RuntimePropertyType;
-            if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1))
+            if (!state.Current.IsProcessingKeyValuePair && !typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1))
             {
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.JsonPath);
             }
@@ -128,7 +128,7 @@ namespace System.Text.Json
                     state.Current.ReturnValue = value;
                     return true;
                 }
-                else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsImmutableDictionary)
+                else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
                 {
                     // Returning a non-converted list.
                     return true;
@@ -207,7 +207,10 @@ namespace System.Text.Json
                     ThrowHelper.ThrowJsonException_DeserializeDuplicateKey(key, reader, state.JsonPath);
                 }
             }
-            else if (state.Current.IsImmutableDictionary || (state.Current.IsImmutableDictionaryProperty && !setPropertyDirectly))
+            else if (state.Current.IsIDictionaryConstructible ||
+                (state.Current.IsIDictionaryConstructibleProperty && !setPropertyDirectly) ||
+                state.Current.IsKeyValuePair ||
+                (state.Current.IsKeyValuePairProperty && !setPropertyDirectly))
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
                 IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.TempDictionaryValues);
@@ -286,7 +289,7 @@ namespace System.Text.Json
                     ThrowHelper.ThrowJsonException_DeserializeDuplicateKey(key, reader, state.JsonPath);
                 }
             }
-            else if (state.Current.IsProcessingImmutableDictionary)
+            else if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
                 IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.TempDictionaryValues;
index 9c7f73d..595be6b 100644 (file)
@@ -4,7 +4,7 @@
 
 using System.Collections;
 using System.Diagnostics;
-using System.Text.Json.Serialization.Converters;
+using System.Text.Json.Serialization.Policies;
 
 namespace System.Text.Json
 {
@@ -40,7 +40,7 @@ namespace System.Text.Json
 
                 JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-                if (state.Current.IsProcessingImmutableDictionary)
+                if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
                 {
                     state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateObject();
                 }
@@ -58,7 +58,7 @@ namespace System.Text.Json
 
             state.Current.PropertyInitialized = true;
 
-            if (state.Current.IsProcessingImmutableDictionary)
+            if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
             {
                 JsonClassInfo dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
                 state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateObject();
@@ -92,10 +92,31 @@ namespace System.Text.Json
                 // We added the items to the dictionary already.
                 state.Current.ResetProperty();
             }
-            else if (state.Current.IsImmutableDictionaryProperty)
+            else if (state.Current.IsIDictionaryConstructibleProperty)
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
-                state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, CreateImmutableDictionaryFromTempValues(ref state, options));
+                JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
+                state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options));
+                state.Current.ResetProperty();
+            }
+            else if (state.Current.IsKeyValuePairProperty)
+            {
+                JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+
+                JsonPropertyInfo propertyInfo;
+                if (elementClassInfo.ClassType == ClassType.KeyValuePair)
+                {
+                    propertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair();
+                }
+                else
+                {
+                    propertyInfo = elementClassInfo.GetPolicyProperty();
+                }
+
+                Debug.Assert(state.Current.TempDictionaryValues != null);
+                state.Current.JsonPropertyInfo.SetValueAsObject(
+                    state.Current.ReturnValue,
+                    propertyInfo.CreateKeyValuePairInstance(ref state, state.Current.TempDictionaryValues, options));
                 state.Current.ResetProperty();
             }
             else
@@ -103,7 +124,27 @@ namespace System.Text.Json
                 object value;
                 if (state.Current.TempDictionaryValues != null)
                 {
-                    value = CreateImmutableDictionaryFromTempValues(ref state, options);
+                    if (state.Current.IsKeyValuePair)
+                    {
+                        JsonClassInfo elementClassInfo = state.Current.JsonClassInfo.ElementClassInfo;
+
+                        JsonPropertyInfo propertyInfo;
+                        if (elementClassInfo.ClassType == ClassType.KeyValuePair)
+                        {
+                            propertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair();
+                        }
+                        else
+                        {
+                            propertyInfo = elementClassInfo.GetPolicyProperty();
+                        }
+
+                        value = propertyInfo.CreateKeyValuePairInstance(ref state, state.Current.TempDictionaryValues, options);
+                    }
+                    else
+                    {
+                        JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
+                        value = converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options);
+                    }
                 }
                 else
                 {
@@ -123,15 +164,5 @@ namespace System.Text.Json
                 }
             }
         }
-
-        private static IDictionary CreateImmutableDictionaryFromTempValues(ref ReadStack state, JsonSerializerOptions options)
-        {
-            Debug.Assert(state.Current.IsProcessingImmutableDictionary);
-
-            DefaultImmutableConverter converter = (DefaultImmutableConverter)state.Current.JsonPropertyInfo.EnumerableConverter;
-            Debug.Assert(converter != null);
-
-            return converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options);
-        }
     }
 }
index a4ed20e..324166d 100644 (file)
@@ -29,13 +29,13 @@ namespace System.Text.Json
                 ThrowHelper.ThrowJsonException_DeserializeCannotBeNull(reader, state.JsonPath);
             }
 
-            if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsImmutableDictionary)
+            if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
             {
                 ApplyObjectToEnumerable(null, ref state, ref reader);
                 return false;
             }
 
-            if (state.Current.IsEnumerableProperty || state.Current.IsDictionaryProperty || state.Current.IsImmutableDictionaryProperty)
+            if (state.Current.IsEnumerableProperty || state.Current.IsDictionaryProperty || state.Current.IsIDictionaryConstructibleProperty)
             {
                 bool setPropertyToNull = !state.Current.PropertyInitialized;
                 ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: setPropertyToNull);
index 18d3359..421c6da 100644 (file)
@@ -11,7 +11,7 @@ namespace System.Text.Json
     {
         private static void HandleStartObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
         {
-            Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingImmutableDictionary);
+            Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair);
 
             if (state.Current.IsProcessingEnumerable)
             {
@@ -42,7 +42,7 @@ namespace System.Text.Json
                 }
             }
 
-            if (state.Current.IsProcessingImmutableDictionary)
+            if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
             {
                 state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateObject();
             }
@@ -54,7 +54,7 @@ namespace System.Text.Json
 
         private static void HandleEndObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
         {
-            Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingImmutableDictionary);
+            Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair);
 
             state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current);
 
index 67f1ae2..11fccd5 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json
             Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default);
             Debug.Assert(state.Current.JsonClassInfo != default);
 
-            if (state.Current.IsProcessingDictionary || state.Current.IsProcessingImmutableDictionary)
+            if (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
             {
                 if (ReferenceEquals(state.Current.JsonClassInfo.DataExtensionProperty, state.Current.JsonPropertyInfo))
                 {
@@ -47,7 +47,7 @@ namespace System.Text.Json
                         keyName = options.DictionaryKeyPolicy.ConvertName(keyName);
                     }
 
-                    if (state.Current.IsDictionary || state.Current.IsImmutableDictionary)
+                    if (state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
                     {
                         state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
                     }
@@ -55,8 +55,10 @@ namespace System.Text.Json
                     Debug.Assert(
                         state.Current.IsDictionary ||
                         (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null) ||
-                        state.Current.IsImmutableDictionary ||
-                        (state.Current.IsImmutableDictionaryProperty && state.Current.JsonPropertyInfo != null));
+                        state.Current.IsIDictionaryConstructible ||
+                        (state.Current.IsIDictionaryConstructibleProperty && state.Current.JsonPropertyInfo != null) ||
+                        state.Current.IsKeyValuePair ||
+                        (state.Current.IsKeyValuePairProperty && state.Current.JsonPropertyInfo != null));
 
                     state.Current.KeyName = keyName;
                 }
index 44c46e8..4e56059 100644 (file)
@@ -66,7 +66,7 @@ namespace System.Text.Json
                                 break;
                             }
                         }
-                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary)
+                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
                         {
                             HandleStartDictionary(options, ref reader, ref readStack);
                         }
@@ -81,7 +81,7 @@ namespace System.Text.Json
                         {
                             readStack.Pop();
                         }
-                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary)
+                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)
                         {
                             HandleEndDictionary(options, ref reader, ref readStack);
                         }
index a7ee061..91b6bdb 100644 (file)
@@ -105,7 +105,7 @@ namespace System.Text.Json
                 value = (TProperty)polymorphicEnumerator.Current.Value;
                 key = polymorphicEnumerator.Current.Key;
             }
-            else if (current.IsImmutableDictionary || current.IsImmutableDictionaryProperty)
+            else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
             {
                 value = (TProperty)((DictionaryEntry)current.Enumerator.Current).Value;
                 key = (string)((DictionaryEntry)current.Enumerator.Current).Key;
index 495d5ce..826843e 100644 (file)
@@ -60,6 +60,12 @@ namespace System.Text.Json
                     // An object or another enumerator requires a new stack frame.
                     object nextValue = state.Current.Enumerator.Current;
                     state.Push(elementClassInfo, nextValue);
+
+                    if (elementClassInfo.ClassType == ClassType.KeyValuePair)
+                    {
+                        state.Current.JsonPropertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair();
+                        state.Current.PropertyIndex++;
+                    }
                 }
 
                 return false;
index b5fa6bb..e01d738 100644 (file)
@@ -51,6 +51,7 @@ namespace System.Text.Json
         {
             Debug.Assert(
                 state.Current.JsonClassInfo.ClassType == ClassType.Object ||
+                state.Current.JsonClassInfo.ClassType == ClassType.KeyValuePair ||
                 state.Current.JsonClassInfo.ClassType == ClassType.Unknown);
 
             JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex);
@@ -105,9 +106,9 @@ namespace System.Text.Json
             }
 
             // A property that returns an immutable dictionary keeps the same stack frame.
-            if (jsonPropertyInfo.ClassType == ClassType.ImmutableDictionary)
+            if (jsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible)
             {
-                state.Current.IsImmutableDictionaryProperty = true;
+                state.Current.IsIDictionaryConstructibleProperty = true;
 
                 bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state);
                 if (endOfEnumerable)
@@ -136,6 +137,13 @@ namespace System.Text.Json
 
                 // Set the PropertyInfo so we can obtain the property name in order to write it.
                 state.Current.JsonPropertyInfo = previousPropertyInfo;
+
+                if (state.Current.JsonPropertyInfo.ClassType == ClassType.KeyValuePair)
+                {
+                    // Advance to the next property, since the first one is the KeyValuePair type itself,
+                    // not its first property (Key or Value).
+                    state.Current.PropertyIndex++;
+                }
             }
             else
             {
index 5d7a5dd..4a737e0 100644 (file)
@@ -33,10 +33,11 @@ namespace System.Text.Json
                         finishedSerializing = true;
                         break;
                     case ClassType.Object:
+                    case ClassType.KeyValuePair:
                         finishedSerializing = WriteObject(options, writer, ref state);
                         break;
                     case ClassType.Dictionary:
-                    case ClassType.ImmutableDictionary:
+                    case ClassType.IDictionaryConstructible:
                         finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
                         break;
                     default:
index 6260191..5fee1f7 100644 (file)
@@ -2,6 +2,7 @@
 // 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.Concurrent;
 using System.Diagnostics;
 using System.Text.Json.Serialization;
@@ -19,6 +20,7 @@ namespace System.Text.Json
 
         private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
         private readonly ConcurrentDictionary<Type, JsonPropertyInfo> _objectJsonProperties = new ConcurrentDictionary<Type, JsonPropertyInfo>();
+        private static ConcurrentDictionary<string, object> s_createRangeDelegates = new ConcurrentDictionary<string, object>();
         private ClassMaterializer _classMaterializerStrategy;
         private JsonNamingPolicy _dictionayKeyPolicy;
         private JsonNamingPolicy _jsonPropertyNamingPolicy;
@@ -306,8 +308,16 @@ namespace System.Text.Json
             };
         }
 
+        internal delegate object ImmutableCreateRangeDelegate<T>(IEnumerable<T> items);
+        internal delegate object ImmutableDictCreateRangeDelegate<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> items);
+
         internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(JsonClassInfo classInfo, JsonSerializerOptions options)
         {
+            if (classInfo.ClassType == ClassType.KeyValuePair)
+            {
+                return classInfo.GetPolicyPropertyOfKeyValuePair();
+            }
+
             if (classInfo.ClassType != ClassType.Object)
             {
                 return classInfo.GetPolicyProperty();
@@ -324,6 +334,22 @@ namespace System.Text.Json
             return propertyInfo;
         }
 
+        internal bool CreateRangeDelegatesContainsKey(string key)
+        {
+            return s_createRangeDelegates.ContainsKey(key);
+        }
+
+        internal bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate)
+        {
+            return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null;
+        }
+
+        internal bool TryAddCreateRangeDelegate(string key, object createRangeDelegate)
+        {
+            return s_createRangeDelegates.TryAdd(key, createRangeDelegate);
+        }
+
+
         private void VerifyMutable()
         {
             // The default options are hidden and thus should be immutable.
index a52c246..b642b95 100644 (file)
@@ -32,7 +32,9 @@ namespace System.Text.Json
         // Has an array or dictionary property been initialized.
         public bool PropertyInitialized;
 
-        // Support Immutable dictionary types.
+        // Support IDictionary constructible types, i.e. types that we
+        // support by passing and IDictionary to their constructors:
+        // immutable dictionaries, Hashtable, SortedList
         public IDictionary TempDictionaryValues;
 
         // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
@@ -42,14 +44,17 @@ namespace System.Text.Json
         // The current JSON data for a property does not match a given POCO, so ignore the property (recursively).
         public bool Drain;
 
-        public bool IsImmutableDictionary => JsonClassInfo.ClassType == ClassType.ImmutableDictionary;
+        public bool IsIDictionaryConstructible => JsonClassInfo.ClassType == ClassType.IDictionaryConstructible;
         public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary;
+        public bool IsKeyValuePair => JsonClassInfo.ClassType == ClassType.KeyValuePair;
 
         public bool IsDictionaryProperty => JsonPropertyInfo != null &&
             !JsonPropertyInfo.IsPropertyPolicy &&
             JsonPropertyInfo.ClassType == ClassType.Dictionary;
-        public bool IsImmutableDictionaryProperty => JsonPropertyInfo != null &&
-            !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.ImmutableDictionary);
+        public bool IsIDictionaryConstructibleProperty => JsonPropertyInfo != null &&
+            !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible);
+        public bool IsKeyValuePairProperty => JsonPropertyInfo != null &&
+            !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.KeyValuePair);
 
         public bool IsEnumerable => JsonClassInfo.ClassType == ClassType.Enumerable;
 
@@ -58,9 +63,12 @@ namespace System.Text.Json
             !JsonPropertyInfo.IsPropertyPolicy &&
             JsonPropertyInfo.ClassType == ClassType.Enumerable;
 
-        public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary || IsProcessingImmutableDictionary;
+        public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary || IsProcessingIDictionaryConstructibleOrKeyValuePair;
+        public bool IsProcessingIDictionaryConstructibleOrKeyValuePair => IsProcessingIDictionaryConstructible || IsProcessingKeyValuePair;
+
         public bool IsProcessingDictionary => IsDictionary || IsDictionaryProperty;
-        public bool IsProcessingImmutableDictionary => IsImmutableDictionary || IsImmutableDictionaryProperty;
+        public bool IsProcessingIDictionaryConstructible => IsIDictionaryConstructible || IsIDictionaryConstructibleProperty;
+        public bool IsProcessingKeyValuePair => IsKeyValuePair || IsKeyValuePairProperty;
         public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty;
 
         public bool IsProcessingValue
@@ -73,6 +81,20 @@ namespace System.Text.Json
                     return false;
                 }
 
+                if (PropertyInitialized)
+                {
+                    if ((IsEnumerable || IsDictionary || IsIDictionaryConstructible) &&
+                        JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown)
+                    {
+                        return true;
+                    }
+                    else if ((IsEnumerableProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty) &&
+                        JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown)
+                    {
+                        return true;
+                    }
+                }
+
                 // We've got a property info. If we're a Value or polymorphic Value
                 // (ClassType.Unknown), return true.
                 ClassType type = JsonPropertyInfo.ClassType;
@@ -80,8 +102,8 @@ namespace System.Text.Json
                     KeyName != null  && (
                     (IsDictionary && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) ||
                     (IsDictionaryProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown) ||
-                    (IsImmutableDictionary && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) ||
-                    (IsImmutableDictionaryProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown)
+                    (IsIDictionaryConstructible && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) ||
+                    (IsIDictionaryConstructibleProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown)
                     );
             }
         }
@@ -97,10 +119,14 @@ namespace System.Text.Json
             if (JsonClassInfo.ClassType == ClassType.Value ||
                 JsonClassInfo.ClassType == ClassType.Enumerable ||
                 JsonClassInfo.ClassType == ClassType.Dictionary ||
-                JsonClassInfo.ClassType == ClassType.ImmutableDictionary)
+                JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
             {
                 JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
             }
+            else if (JsonClassInfo.ClassType == ClassType.KeyValuePair)
+            {
+                JsonPropertyInfo = JsonClassInfo.GetPolicyPropertyOfKeyValuePair();
+            }
         }
 
         public void Reset()
@@ -151,8 +177,8 @@ namespace System.Text.Json
                 return null;
             }
 
-            Type propType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            if (typeof(IList).IsAssignableFrom(propType))
+            Type propertyType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+            if (typeof(IList).IsAssignableFrom(propertyType))
             {
                 // If IList, add the members as we create them.
                 JsonClassInfo collectionClassInfo = state.Current.JsonPropertyInfo.RuntimeClassInfo;
@@ -161,19 +187,19 @@ namespace System.Text.Json
             }
             else
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propType, reader, state.JsonPath);
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType, reader, state.JsonPath);
                 return null;
             }
         }
 
         public Type GetElementType()
         {
-            if (IsEnumerableProperty || IsDictionaryProperty || IsImmutableDictionaryProperty)
+            if (IsEnumerableProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty)
             {
                 return JsonPropertyInfo.ElementClassInfo.Type;
             }
 
-            if (IsEnumerable || IsDictionary || IsImmutableDictionary)
+            if (IsEnumerable || IsDictionary || IsIDictionaryConstructible)
             {
                 return JsonClassInfo.ElementClassInfo.Type;
             }
index 66abff3..065a1eb 100644 (file)
@@ -6,7 +6,6 @@
 using System.Diagnostics;
 using System.Reflection;
 using System.Reflection.Emit;
-using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json
 {
@@ -60,7 +59,7 @@ namespace System.Text.Json
             }
 
             return createRange.CreateDelegate(
-                typeof(DefaultImmutableConverter.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null);
+                typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null);
         }
 
         public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType)
@@ -73,7 +72,7 @@ namespace System.Text.Json
             }
 
             return createRange.CreateDelegate(
-                typeof(DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null);
+                typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null);
         }
     }
 }
index 1750f5a..22e79dd 100644 (file)
@@ -4,7 +4,6 @@
 
 using System.Diagnostics;
 using System.Reflection;
-using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json
 {
@@ -33,7 +32,7 @@ namespace System.Text.Json
             }
 
             return createRange.CreateDelegate(
-                typeof(DefaultImmutableConverter.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null);
+                typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null);
         }
 
         public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType)
@@ -46,7 +45,7 @@ namespace System.Text.Json
             }
 
             return createRange.CreateDelegate(
-                typeof(DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null);
+                typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null);
         }
     }
 }
index 9f7dd4d..95b2df1 100644 (file)
@@ -52,16 +52,16 @@ namespace System.Text.Json
                 Current.PopStackOnEnd = true;
                 Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty();
             }
-            else if (classType == ClassType.ImmutableDictionary)
+            else if (classType == ClassType.IDictionaryConstructible)
             {
                 Current.PopStackOnEnd = true;
                 Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty();
 
-                Current.IsImmutableDictionary = true;
+                Current.IsIDictionaryConstructible = true;
             }
             else
             {
-                Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown);
+                Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.KeyValuePair || nextClassInfo.ClassType == ClassType.Unknown);
                 Current.PopStackOnEndObject = true;
             }
         }
index 2526401..e7e1e50 100644 (file)
@@ -2,10 +2,8 @@
 // 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.Buffers;
 using System.Collections;
 using System.Diagnostics;
-using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json
 {
@@ -18,9 +16,9 @@ namespace System.Text.Json
         // Support Dictionary keys.
         public string KeyName;
 
-        // Whether the current object is an immutable dictionary.
-        public bool IsImmutableDictionary;
-        public bool IsImmutableDictionaryProperty;
+        // Whether the current object can be constructed with IDictionary.
+        public bool IsIDictionaryConstructible;
+        public bool IsIDictionaryConstructibleProperty;
 
         // The current enumerator for the IEnumerable or IDictionary.
         public IEnumerator Enumerator;
@@ -47,10 +45,17 @@ namespace System.Text.Json
             {
                 JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
             }
-            else if (JsonClassInfo.ClassType == ClassType.ImmutableDictionary)
+            else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
             {
                 JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
-                IsImmutableDictionary = true;
+                IsIDictionaryConstructible = true;
+            }
+            else if (JsonClassInfo.ClassType == ClassType.KeyValuePair)
+            {
+                JsonPropertyInfo = JsonClassInfo.GetPolicyPropertyOfKeyValuePair();
+                // Advance to the next property, since the first one is the KeyValuePair type itself,
+                // not its first property (Key or Value).
+                PropertyIndex++;
             }
         }
 
@@ -70,7 +75,7 @@ namespace System.Text.Json
                 Debug.Assert(writeNull == false);
 
                 // Write start without a property name.
-                if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.ImmutableDictionary)
+                if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible)
                 {
                     writer.WriteStartObject();
                     StartObjectWritten = true;
@@ -91,7 +96,7 @@ namespace System.Text.Json
             }
             else if (classType == ClassType.Object ||
                 classType == ClassType.Dictionary ||
-                classType == ClassType.ImmutableDictionary)
+                classType == ClassType.IDictionaryConstructible)
             {
                 writer.WriteStartObject(propertyName);
                 StartObjectWritten = true;
@@ -111,7 +116,7 @@ namespace System.Text.Json
             JsonClassInfo = null;
             JsonPropertyInfo = null;
             PropertyIndex = 0;
-            IsImmutableDictionary = false;
+            IsIDictionaryConstructible = false;
             PopStackOnEndObject = false;
             PopStackOnEnd = false;
             StartObjectWritten = false;
@@ -121,7 +126,7 @@ namespace System.Text.Json
         {
             PropertyIndex = 0;
             PopStackOnEndObject = false;
-            IsImmutableDictionaryProperty = false;
+            IsIDictionaryConstructibleProperty = false;
             JsonPropertyInfo = null;
         }
 
index bd505b5..4e8b1fe 100644 (file)
@@ -42,6 +42,13 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
+        public static JsonException GetJsonException_DeserializeUnableToConvertValue(Type propertyType, string path)
+        {
+            string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType) + $" Path: {path}.";
+            return new JsonException(message, path, null, null);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowJsonException_DeserializeCannotBeNull(in Utf8JsonReader reader, string path)
         {
             ThowJsonException(SR.DeserializeCannotBeNull, in reader, path);
index aeddfbb..d19318f 100644 (file)
@@ -268,6 +268,27 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadClassWithObjectIEnumerable()
+        {
+            TestClassWithObjectIEnumerable obj = JsonSerializer.Parse<TestClassWithObjectIEnumerable>(TestClassWithObjectIEnumerable.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithObjectIList()
+        {
+            TestClassWithObjectIList obj = JsonSerializer.Parse<TestClassWithObjectIList>(TestClassWithObjectIList.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithObjectICollection()
+        {
+            TestClassWithObjectICollection obj = JsonSerializer.Parse<TestClassWithObjectICollection>(TestClassWithObjectICollection.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
         public static void ReadClassWithObjectIEnumerableT()
         {
             TestClassWithObjectIEnumerableT obj = JsonSerializer.Parse<TestClassWithObjectIEnumerableT>(TestClassWithObjectIEnumerableT.s_data);
@@ -303,6 +324,32 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadClassWithGenericIEnumerable()
+        {
+            TestClassWithGenericIEnumerable obj = JsonSerializer.Parse<TestClassWithGenericIEnumerable>(TestClassWithGenericIEnumerable.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithGenericIList()
+        {
+            TestClassWithGenericIList obj = JsonSerializer.Parse<TestClassWithGenericIList>(TestClassWithGenericIList.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithGenericICollection()
+        {
+            TestClassWithGenericICollection obj = JsonSerializer.Parse<TestClassWithGenericICollection>(TestClassWithGenericICollection.s_data);
+        }
+
+        public static void ReadClassWithObjectISetT()
+        {
+            TestClassWithObjectISetT obj = JsonSerializer.Parse<TestClassWithObjectISetT>(TestClassWithObjectISetT.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
         public static void ReadClassWithGenericIEnumerableT()
         {
             TestClassWithGenericIEnumerableT obj = JsonSerializer.Parse<TestClassWithGenericIEnumerableT>(TestClassWithGenericIEnumerableT.s_data);
@@ -338,6 +385,13 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadClassWithGenericISetT()
+        {
+            TestClassWithGenericISetT obj = JsonSerializer.Parse<TestClassWithGenericISetT>(TestClassWithGenericISetT.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
         public static void ReadClassWithObjectIEnumerableConstructibleTypes()
         {
             TestClassWithObjectIEnumerableConstructibleTypes obj = JsonSerializer.Parse<TestClassWithObjectIEnumerableConstructibleTypes>(TestClassWithObjectIEnumerableConstructibleTypes.s_data);
index fbe5a18..544c24b 100644 (file)
@@ -18,6 +18,18 @@ namespace System.Text.Json.Serialization.Tests
             const string ReorderedJsonString = @"{""Hello2"":""World2"",""Hello"":""World""}";
 
             {
+                IDictionary obj = JsonSerializer.Parse<IDictionary>(JsonString);
+                Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString());
+                Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString());
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(JsonString, json);
+
+                json = JsonSerializer.ToString<object>(obj);
+                Assert.Equal(JsonString, json);
+            }
+
+            {
                 Dictionary<string, string> obj = JsonSerializer.Parse<Dictionary<string, string>>(JsonString);
                 Assert.Equal("World", obj["Hello"]);
                 Assert.Equal("World2", obj["Hello2"]);
@@ -30,6 +42,18 @@ namespace System.Text.Json.Serialization.Tests
             }
 
             {
+                SortedDictionary<string, string> obj = JsonSerializer.Parse<SortedDictionary<string, string>>(JsonString);
+                Assert.Equal("World", obj["Hello"]);
+                Assert.Equal("World2", obj["Hello2"]);
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(JsonString, json);
+
+                json = JsonSerializer.ToString<object>(obj);
+                Assert.Equal(JsonString, json);
+            }
+
+            {
                 IDictionary<string, string> obj = JsonSerializer.Parse<IDictionary<string, string>>(JsonString);
                 Assert.Equal("World", obj["Hello"]);
                 Assert.Equal("World2", obj["Hello2"]);
@@ -88,11 +112,39 @@ namespace System.Text.Json.Serialization.Tests
                 json = JsonSerializer.ToString<object>(obj);
                 Assert.True(JsonString == json);
             }
+
+            {
+                Hashtable obj = JsonSerializer.Parse<Hashtable>(JsonString);
+                Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString());
+                Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString());
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.True(JsonString == json || ReorderedJsonString == json);
+
+                json = JsonSerializer.ToString<object>(obj);
+                Assert.True(JsonString == json || ReorderedJsonString == json);
+            }
+
+            {
+                SortedList obj = JsonSerializer.Parse<SortedList>(JsonString);
+                Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString());
+                Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString());
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(JsonString, json);
+
+                json = JsonSerializer.ToString<object>(obj);
+                Assert.Equal(JsonString, json);
+            }
         }
 
         [Fact]
         public static void DuplicateKeysFail()
         {
+            // Non-generic IDictionary case.
+            Assert.Throws<JsonException>(() => JsonSerializer.Parse<IDictionary>(
+                @"{""Hello"":""World"", ""Hello"":""World""}"));
+
             // Strongly-typed IDictionary<,> case.
             Assert.Throws<JsonException>(() => JsonSerializer.Parse<Dictionary<string, string>>(
                 @"{""Hello"":""World"", ""Hello"":""World""}"));
@@ -105,14 +157,27 @@ namespace System.Text.Json.Serialization.Tests
         [Fact]
         public static void DictionaryOfObject()
         {
-            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);
-            Assert.Equal(1, element.GetInt32());
+            {
+                IDictionary obj = JsonSerializer.Parse<IDictionary>(@"{""Key1"":1}");
+                Assert.Equal(1, obj.Count);
+                JsonElement element = (JsonElement)obj["Key1"];
+                Assert.Equal(JsonValueType.Number, element.Type);
+                Assert.Equal(1, element.GetInt32());
 
-            string json = JsonSerializer.ToString(obj);
-            Assert.Equal(@"{""Key1"":1}", json);
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(@"{""Key1"":1}", json);
+            }
+
+            {
+                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);
+                Assert.Equal(1, element.GetInt32());
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(@"{""Key1"":1}", json);
+            }
         }
 
         [Theory]
@@ -200,6 +265,29 @@ namespace System.Text.Json.Serialization.Tests
             const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}";
 
             {
+                IDictionary obj = JsonSerializer.Parse<IDictionary>(JsonString);
+
+                Assert.Equal(2, obj.Count);
+
+                int expectedNumber = 1;
+
+                JsonElement element = (JsonElement)obj["Key1"];
+                foreach (JsonElement value in element.EnumerateArray())
+                {
+                    Assert.Equal(expectedNumber++, value.GetInt32());
+                }
+
+                element = (JsonElement)obj["Key2"];
+                foreach (JsonElement value in element.EnumerateArray())
+                {
+                    Assert.Equal(expectedNumber++, value.GetInt32());
+                }
+
+                string json = JsonSerializer.ToString(obj);
+                Assert.Equal(JsonString, json);
+            }
+
+            {
                 IDictionary<string, List<int>> obj = JsonSerializer.Parse<IDictionary<string, List<int>>>(JsonString);
 
                 Assert.Equal(2, obj.Count);
@@ -461,6 +549,63 @@ namespace System.Text.Json.Serialization.Tests
         public static void DictionaryOfClasses()
         {
             {
+                IDictionary obj;
+
+                {
+                    string json = @"{""Key1"":" + SimpleTestClass.s_json + @",""Key2"":" + SimpleTestClass.s_json + "}";
+                    obj = JsonSerializer.Parse<IDictionary>(json);
+                    Assert.Equal(2, obj.Count);
+
+                    if (obj["Key1"] is JsonElement element)
+                    {
+                        SimpleTestClass result = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                        result.Verify();
+                    }
+                    else
+                    {
+                        ((SimpleTestClass)obj["Key1"]).Verify();
+                        ((SimpleTestClass)obj["Key2"]).Verify();
+                    }
+                }
+
+                {
+                    // We can't compare against the json string above because property ordering is not deterministic (based on reflection order)
+                    // so just round-trip the json and compare.
+                    string json = JsonSerializer.ToString(obj);
+                    obj = JsonSerializer.Parse<IDictionary>(json);
+                    Assert.Equal(2, obj.Count);
+
+                    if (obj["Key1"] is JsonElement element)
+                    {
+                        SimpleTestClass result = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                        result.Verify();
+                    }
+                    else
+                    {
+                        ((SimpleTestClass)obj["Key1"]).Verify();
+                        ((SimpleTestClass)obj["Key2"]).Verify();
+                    }
+                }
+
+                {
+                    string json = JsonSerializer.ToString<object>(obj);
+                    obj = JsonSerializer.Parse<IDictionary>(json);
+                    Assert.Equal(2, obj.Count);
+
+                    if (obj["Key1"] is JsonElement element)
+                    {
+                        SimpleTestClass result = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                        result.Verify();
+                    }
+                    else
+                    {
+                        ((SimpleTestClass)obj["Key1"]).Verify();
+                        ((SimpleTestClass)obj["Key2"]).Verify();
+                    }
+                }
+            }
+
+            {
                 Dictionary<string, SimpleTestClass> obj;
 
                 {
@@ -601,30 +746,6 @@ namespace System.Text.Json.Serialization.Tests
         public static void HashtableFail()
         {
             {
-                string json = @"{""Key"":""Value""}";
-
-                // Verify we can deserialize into Dictionary<,>
-                JsonSerializer.Parse<Dictionary<string, string>>(json);
-
-                // We don't support non-generic IDictionary
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<Hashtable>(json));
-            }
-
-            {
-                Hashtable ht = new Hashtable();
-                ht.Add("Key", "Value");
-
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.ToString(ht));
-            }
-
-            {
-                string json = @"{""Key"":""Value""}";
-
-                // We don't support non-generic IDictionary
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<IDictionary>(json));
-            }
-
-            {
                 IDictionary ht = new Hashtable();
                 ht.Add("Key", "Value");
                 Assert.Throws<NotSupportedException>(() => JsonSerializer.ToString(ht));
index ed3696e..3913736 100644 (file)
@@ -2,6 +2,7 @@
 // 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 Xunit;
 
 namespace System.Text.Json.Serialization.Tests
index 086e7cf..7b35350 100644 (file)
@@ -2,6 +2,7 @@
 // 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;
 using Xunit;
@@ -95,39 +96,67 @@ namespace System.Text.Json.Serialization.Tests
             json = JsonSerializer.ToString<object>(list);
             Assert.Equal(ExpectedJson, json);
 
-            IEnumerable<object> ienumerable = new List<object> { 1, true, address, null, "foo" };
+            IEnumerable ienumerable = new List<object> { 1, true, address, null, "foo" };
             json = JsonSerializer.ToString(ienumerable);
             Assert.Equal(ExpectedJson, json);
 
             json = JsonSerializer.ToString<object>(ienumerable);
             Assert.Equal(ExpectedJson, json);
 
-            IList<object> ilist = new List<object> { 1, true, address, null, "foo" };
+            IList ilist = new List<object> { 1, true, address, null, "foo" };
             json = JsonSerializer.ToString(ilist);
             Assert.Equal(ExpectedJson, json);
 
             json = JsonSerializer.ToString<object>(ilist);
             Assert.Equal(ExpectedJson, json);
 
-            ICollection<object> icollection = new List<object> { 1, true, address, null, "foo" };
+            ICollection icollection = new List<object> { 1, true, address, null, "foo" };
             json = JsonSerializer.ToString(icollection);
             Assert.Equal(ExpectedJson, json);
 
             json = JsonSerializer.ToString<object>(icollection);
             Assert.Equal(ExpectedJson, json);
 
-            IReadOnlyCollection<object> ireadonlycollection = new List<object> { 1, true, address, null, "foo" };
-            json = JsonSerializer.ToString(ireadonlycollection);
+            IEnumerable<object> genericIEnumerable = new List<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(genericIEnumerable);
             Assert.Equal(ExpectedJson, json);
 
-            json = JsonSerializer.ToString<object>(ireadonlycollection);
+            json = JsonSerializer.ToString<object>(genericIEnumerable);
             Assert.Equal(ExpectedJson, json);
 
-            IReadOnlyList<object> ireadonlylist = new List<object> { 1, true, address, null, "foo" };
-            json = JsonSerializer.ToString(ireadonlylist);
+            IList<object> genericIList = new List<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(genericIList);
             Assert.Equal(ExpectedJson, json);
 
-            json = JsonSerializer.ToString<object>(ireadonlylist);
+            json = JsonSerializer.ToString<object>(genericIList);
+            Assert.Equal(ExpectedJson, json);
+
+            ICollection<object> genericICollection = new List<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(genericICollection);
+            Assert.Equal(ExpectedJson, json);
+
+            json = JsonSerializer.ToString<object>(genericICollection);
+            Assert.Equal(ExpectedJson, json);
+
+            IReadOnlyCollection<object> genericIReadOnlyCollection = new List<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(genericIReadOnlyCollection);
+            Assert.Equal(ExpectedJson, json);
+
+            json = JsonSerializer.ToString<object>(genericIReadOnlyCollection);
+            Assert.Equal(ExpectedJson, json);
+
+            IReadOnlyList<object> genericIReadonlyList = new List<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(genericIReadonlyList);
+            Assert.Equal(ExpectedJson, json);
+
+            json = JsonSerializer.ToString<object>(genericIReadonlyList);
+            Assert.Equal(ExpectedJson, json);
+
+            ISet<object> iset = new HashSet<object> { 1, true, address, null, "foo" };
+            json = JsonSerializer.ToString(iset);
+            Assert.Equal(ExpectedJson, json);
+
+            json = JsonSerializer.ToString<object>(iset);
             Assert.Equal(ExpectedJson, json);
 
             Stack<object> stack = new Stack<object>(new List<object> { 1, true, address, null, "foo" });
@@ -259,11 +288,15 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Contains(@"""Address"":{""City"":""MyCity""}", json);
                 Assert.Contains(@"""List"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""Array"":[""Hello"",""Again""]", json);
+                Assert.Contains(@"""IEnumerable"":[""Hello"",""World""]", json);
+                Assert.Contains(@"""IList"":[""Hello"",""World""]", json);
+                Assert.Contains(@"""ICollection"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""IEnumerableT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""IListT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""ICollectionT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""IReadOnlyCollectionT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""IReadOnlyListT"":[""Hello"",""World""]", json);
+                Assert.Contains(@"""ISetT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""StackT"":[""World"",""Hello""]", json);
                 Assert.Contains(@"""QueueT"":[""Hello"",""World""]", json);
                 Assert.Contains(@"""HashSetT"":[""Hello"",""World""]", json);
index 23708f6..7578ecc 100644 (file)
@@ -111,11 +111,15 @@ namespace System.Text.Json.Serialization.Tests
         public object /*Address*/ Address { get; set; }
         public object /*List<string>*/ List { get; set; }
         public object /*string[]*/ Array { get; set; }
+        public object /*IEnumerable of strings*/ IEnumerable { get; set; }
+        public object /*IList of strings */ IList { get; set; }
+        public object /*ICollection of strings */ ICollection { get; set; }
         public object /*IEnumerable<string>*/ IEnumerableT { get; set; }
         public object /*IList<string>*/ IListT { get; set; }
         public object /*ICollection<string>*/ ICollectionT { get; set; }
         public object /*IReadOnlyCollection<string>*/ IReadOnlyCollectionT { get; set; }
         public object /*IReadOnlyList<string>*/ IReadOnlyListT { get; set; }
+        public object /*ISet<string>*/ ISetT { get; set; }
         public object /*Stack<string>*/ StackT { get; set; }
         public object /*Queue<string>*/ QueueT { get; set; }
         public object /*HashSet<string>*/ HashSetT { get; set; }
@@ -141,11 +145,15 @@ namespace System.Text.Json.Serialization.Tests
 
             List = new List<string> { "Hello", "World" };
             Array = new string[] { "Hello", "Again" };
+            IEnumerable = new List<string> { "Hello", "World" };
+            IList = new List<string> { "Hello", "World" };
+            ICollection = new List<string> { "Hello", "World" };
             IEnumerableT = new List<string> { "Hello", "World" };
             IListT = new List<string> { "Hello", "World" };
             ICollectionT = new List<string> { "Hello", "World" };
             IReadOnlyCollectionT = new List<string> { "Hello", "World" };
             IReadOnlyListT = new List<string> { "Hello", "World" };
+            ISetT = new HashSet<string> { "Hello", "World" };
             StackT = new Stack<string>(new List<string> { "Hello", "World" });
             QueueT = new Queue<string>(new List<string> { "Hello", "World" });
             HashSetT = new HashSet<string>(new List<string> { "Hello", "World" });
index 8049d12..d0367ef 100644 (file)
@@ -2,6 +2,7 @@
 // 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;
 using System.Linq;
@@ -59,14 +60,20 @@ namespace System.Text.Json.Serialization.Tests
         public int[][][] MyInt16ThreeDimensionArray { get; set; }
         public List<List<List<int>>> MyInt16ThreeDimensionList { get; set; }
         public List<string> MyStringList { get; set; }
+        public IEnumerable MyStringIEnumerable { get; set; }
+        public IList MyStringIList { get; set; }
+        public ICollection MyStringICollection { get; set; }
         public IEnumerable<string> MyStringIEnumerableT { get; set; }
         public IList<string> MyStringIListT { get; set; }
         public ICollection<string> MyStringICollectionT { get; set; }
         public IReadOnlyCollection<string> MyStringIReadOnlyCollectionT { get; set; }
         public IReadOnlyList<string> MyStringIReadOnlyListT { get; set; }
-        public Dictionary<string, string> MyStringToStringDict { get; set; }
-        public IDictionary<string, string> MyStringToStringIDict { get; set; }
-        public IReadOnlyDictionary<string, string> MyStringToStringIReadOnlyDict { get; set; }
+        public ISet<string> MyStringISetT { get; set; }
+        public KeyValuePair<string, string> MyStringToStringKeyValuePair { get; set; }
+        public IDictionary MyStringToStringIDict { get; set; }
+        public Dictionary<string, string> MyStringToStringGenericDict { get; set; }
+        public IDictionary<string, string> MyStringToStringGenericIDict { get; set; }
+        public IReadOnlyDictionary<string, string> MyStringToStringGenericIReadOnlyDict { get; set; }
         public ImmutableDictionary<string, string> MyStringToStringImmutableDict { get; set; }
         public IImmutableDictionary<string, string> MyStringToStringIImmutableDict { get; set; }
         public ImmutableSortedDictionary<string, string> MyStringToStringImmutableSortedDict { get; set; }
@@ -108,9 +115,11 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," +
                 @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," +
                 @"""MyEnum"" : 2," + // int by default
-                @"""MyStringToStringDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," +
                 @"""MyStringToStringIDict"" : {""key"" : ""value""}," +
-                @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericIReadOnlyDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringImmutableDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringImmutableSortedDict"" : {""key"" : ""value""}";
@@ -139,11 +148,15 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyInt16ThreeDimensionArray"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," +
                 @"""MyInt16ThreeDimensionList"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," +
                 @"""MyStringList"" : [""Hello""]," +
+                @"""MyStringIEnumerable"" : [""Hello""]," +
+                @"""MyStringIList"" : [""Hello""]," +
+                @"""MyStringICollection"" : [""Hello""]," +
                 @"""MyStringIEnumerableT"" : [""Hello""]," +
                 @"""MyStringIListT"" : [""Hello""]," +
                 @"""MyStringICollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyListT"" : [""Hello""]," +
+                @"""MyStringISetT"" : [""Hello""]," +
                 @"""MyStringStackT"" : [""Hello"", ""World""]," +
                 @"""MyStringQueueT"" : [""Hello"", ""World""]," +
                 @"""MyStringHashSetT"" : [""Hello""]," +
@@ -229,19 +242,28 @@ namespace System.Text.Json.Serialization.Tests
             list2.Add(new List<int> { 23, 24 });
 
             MyStringList = new List<string>() { "Hello" };
+
+            MyStringIEnumerable = new string[] { "Hello" };
+            MyStringIList = new string[] { "Hello" };
+            MyStringICollection = new string[] { "Hello" };
+
             MyStringIEnumerableT = new string[] { "Hello" };
             MyStringIListT = new string[] { "Hello" };
             MyStringICollectionT = new string[] { "Hello" };
             MyStringIReadOnlyCollectionT = new string[] { "Hello" };
             MyStringIReadOnlyListT = new string[] { "Hello" };
+            MyStringISetT = new HashSet<string> { "Hello" };
 
-            MyStringToStringDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringKeyValuePair = new KeyValuePair<string, string>("myKey", "myValue");
             MyStringToStringIDict = new Dictionary<string, string> { { "key", "value" } };
-            MyStringToStringIReadOnlyDict = new Dictionary<string, string> { { "key", "value" } };
 
-            MyStringToStringImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringDict);
-            MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringDict);
-            MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange(MyStringToStringDict);
+            MyStringToStringGenericDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringGenericIDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringGenericIReadOnlyDict = new Dictionary<string, string> { { "key", "value" } };
+
+            MyStringToStringImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict);
+            MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict);
+            MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange(MyStringToStringGenericDict);
 
             MyStringStackT = new Stack<string>(new List<string>() { "Hello", "World" } );
             MyStringQueueT = new Queue<string>(new List<string>() { "Hello", "World" });
@@ -331,20 +353,99 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(24, MyInt16ThreeDimensionList[1][1][1]);
 
             Assert.Equal("Hello", MyStringList[0]);
+
+            IEnumerator enumerator = MyStringIEnumerable.GetEnumerator();
+            enumerator.MoveNext();
+            {
+                // Verifying after deserialization.
+                if (enumerator.Current is JsonElement currentJsonElement)
+                {
+                    Assert.Equal("Hello", currentJsonElement.GetString());
+                }
+                // Verifying test data.
+                else
+                {
+                    Assert.Equal("Hello", enumerator.Current);
+                }
+            }
+
+            {
+                // Verifying after deserialization.
+                if (MyStringIList[0] is JsonElement currentJsonElement)
+                {
+                    Assert.Equal("Hello", currentJsonElement.GetString());
+                }
+                // Verifying test data.
+                else
+                {
+                    Assert.Equal("Hello", enumerator.Current);
+                }
+            }
+
+            enumerator = MyStringICollection.GetEnumerator();
+            enumerator.MoveNext();
+            {
+                // Verifying after deserialization.
+                if (enumerator.Current is JsonElement currentJsonElement)
+                {
+                    Assert.Equal("Hello", currentJsonElement.GetString());
+                }
+                // Verifying test data.
+                else
+                {
+                    Assert.Equal("Hello", enumerator.Current);
+                }
+            }
+
             Assert.Equal("Hello", MyStringIEnumerableT.First());
             Assert.Equal("Hello", MyStringIListT[0]);
             Assert.Equal("Hello", MyStringICollectionT.First());
             Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First());
             Assert.Equal("Hello", MyStringIReadOnlyListT[0]);
+            Assert.Equal("Hello", MyStringISetT.First());
 
-            Assert.Equal("value", MyStringToStringDict["key"]);
-            Assert.Equal("value", MyStringToStringIDict["key"]);
-            Assert.Equal("value", MyStringToStringIReadOnlyDict["key"]);
+            enumerator = MyStringToStringIDict.GetEnumerator();
+            enumerator.MoveNext();
+            {
+                // Verifying after deserialization.
+                if (enumerator.Current is JsonElement currentJsonElement)
+                {
+                    IEnumerator jsonEnumerator = currentJsonElement.EnumerateObject();
+                    jsonEnumerator.MoveNext();
+
+                    JsonProperty property = (JsonProperty)jsonEnumerator.Current; 
+
+                    Assert.Equal("key", property.Name);
+                    Assert.Equal("value", property.Value.GetString());
+                }
+                // Verifying test data.
+                else
+                {
+                    DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
+                    Assert.Equal("key", entry.Key);
+
+                    if (entry.Value is JsonElement element)
+                    {
+                        Assert.Equal("value", element.GetString());
+                    }
+                    else
+                    {
+                        Assert.Equal("value", entry.Value);
+                    }
+                }
+            }
+
+            Assert.Equal("value", MyStringToStringGenericDict["key"]);
+            Assert.Equal("value", MyStringToStringGenericIDict["key"]);
+            Assert.Equal("value", MyStringToStringGenericIReadOnlyDict["key"]);
 
             Assert.Equal("value", MyStringToStringImmutableDict["key"]);
             Assert.Equal("value", MyStringToStringIImmutableDict["key"]);
             Assert.Equal("value", MyStringToStringImmutableSortedDict["key"]);
 
+            Assert.Equal("myKey", MyStringToStringKeyValuePair.Key);
+            Assert.Equal("myValue", MyStringToStringKeyValuePair.Value);
+
             Assert.Equal(2, MyStringStackT.Count);
             Assert.True(MyStringStackT.Contains("Hello"));
             Assert.True(MyStringStackT.Contains("World"));
index 1417945..dc21f4f 100644 (file)
@@ -2,6 +2,7 @@
 // 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;
 using System.Linq;
@@ -29,14 +30,20 @@ namespace System.Text.Json.Serialization.Tests
         public object MyDateTimeArray { get; set; }
         public object MyEnumArray { get; set; }
         public object MyStringList { get; set; }
+        public object MyStringIEnumerable { get; set; }
+        public object MyStringIList { get; set; }
+        public object MyStringICollection { get; set; }
         public object MyStringIEnumerableT { get; set; }
         public object MyStringIListT { get; set; }
         public object MyStringICollectionT { get; set; }
         public object MyStringIReadOnlyCollectionT { get; set; }
         public object MyStringIReadOnlyListT { get; set; }
-        public object MyStringToStringDict { get; set; }
+        public object MyStringISetT { get; set; }
+        public object MyStringToStringKeyValuePair { get; set; }
         public object MyStringToStringIDict { get; set; }
-        public object MyStringToStringIReadOnlyDict { get; set; }
+        public object MyStringToStringGenericDict { get; set; }
+        public object MyStringToStringGenericIDict { get; set; }
+        public object MyStringToStringGenericIReadOnlyDict { get; set; }
         public object MyStringToStringImmutableDict { get; set; }
         public object MyStringToStringIImmutableDict { get; set; }
         public object MyStringToStringImmutableSortedDict { get; set; }
@@ -92,14 +99,20 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," +
                 @"""MyEnumArray"" : [2]," + // int by default
                 @"""MyStringList"" : [""Hello""]," +
+                @"""MyStringIEnumerable"" : [""Hello""]," +
+                @"""MyStringIList"" : [""Hello""]," +
+                @"""MyStringICollection"" : [""Hello""]," +
                 @"""MyStringIEnumerableT"" : [""Hello""]," +
                 @"""MyStringIListT"" : [""Hello""]," +
                 @"""MyStringICollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyListT"" : [""Hello""]," +
-                @"""MyStringToStringDict"" : {""key"" : ""value""}," +
+                @"""MyStringISetT"" : [""Hello""]," +
+                @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," +
                 @"""MyStringToStringIDict"" : {""key"" : ""value""}," +
-                @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," +
+                @"""MyStringToStringGenericIReadOnlyDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringImmutableDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," +
                 @"""MyStringToStringImmutableSortedDict"" : {""key"" : ""value""}," +
@@ -144,19 +157,30 @@ namespace System.Text.Json.Serialization.Tests
             MyEnumArray = new SampleEnum[] { SampleEnum.Two };
 
             MyStringList = new List<string>() { "Hello" };
+
+            MyStringIEnumerable = new string[] { "Hello" };
+            MyStringIList = new string[] { "Hello" };
+            MyStringICollection = new string[] { "Hello" };
+
             MyStringIEnumerableT = new string[] { "Hello" };
             MyStringIListT = new string[] { "Hello" };
             MyStringICollectionT = new string[] { "Hello" };
             MyStringIReadOnlyCollectionT = new string[] { "Hello" };
             MyStringIReadOnlyListT = new string[] { "Hello" };
+            MyStringIReadOnlyListT = new HashSet<string> { "Hello" };
+
+            MyStringISetT = new HashSet<string> { "Hello" };
 
-            MyStringToStringDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringKeyValuePair = new KeyValuePair<string, string>("myKey", "myValue");
             MyStringToStringIDict = new Dictionary<string, string> { { "key", "value" } };
-            MyStringToStringIReadOnlyDict = new Dictionary<string, string> { { "key", "value" } };
 
-            MyStringToStringImmutableDict = ImmutableDictionary.CreateRange((Dictionary<string, string>)MyStringToStringDict);
-            MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange((Dictionary<string, string>)MyStringToStringDict);
-            MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange((Dictionary<string, string>)MyStringToStringDict);
+            MyStringToStringGenericDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringGenericIDict = new Dictionary<string, string> { { "key", "value" } };
+            MyStringToStringGenericIReadOnlyDict = new Dictionary<string, string> { { "key", "value" } };
+
+            MyStringToStringImmutableDict = ImmutableDictionary.CreateRange((Dictionary<string, string>)MyStringToStringGenericDict);
+            MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange((Dictionary<string, string>)MyStringToStringGenericDict);
+            MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange((Dictionary<string, string>)MyStringToStringGenericDict);
 
             MyStringStackT = new Stack<string>(new List<string>() { "Hello", "World" });
             MyStringQueueT = new Queue<string>(new List<string>() { "Hello", "World" });
@@ -198,20 +222,41 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(SampleEnum.Two, ((SampleEnum[])MyEnumArray)[0]);
 
             Assert.Equal("Hello", ((List<string>)MyStringList)[0]);
+
+            ((IEnumerable)MyStringIEnumerable).GetEnumerator().MoveNext();
+            Assert.Equal("Hello", (string)((IEnumerable)MyStringIEnumerable).GetEnumerator().Current);
+
+            Assert.Equal("Hello", (string)((IList)MyStringIList)[0]);
+
+            ((ICollection)MyStringICollection).GetEnumerator().MoveNext();
+            Assert.Equal("Hello", (string)((ICollection)MyStringICollection).GetEnumerator().Current);
+
             Assert.Equal("Hello", ((IEnumerable<string>)MyStringIEnumerableT).First());
             Assert.Equal("Hello", ((IList<string>)MyStringIListT)[0]);
             Assert.Equal("Hello", ((ICollection<string>)MyStringICollectionT).First());
             Assert.Equal("Hello", ((IReadOnlyCollection<string>)MyStringIReadOnlyCollectionT).First());
             Assert.Equal("Hello", ((IReadOnlyList<string>)MyStringIReadOnlyListT)[0]);
+            Assert.Equal("Hello", ((ISet<string>)MyStringISetT).First());
+
 
-            Assert.Equal("value", ((Dictionary<string, string>)MyStringToStringDict)["key"]);
-            Assert.Equal("value", ((IDictionary<string, string>)MyStringToStringIDict)["key"]);
-            Assert.Equal("value", ((IReadOnlyDictionary<string, string>)MyStringToStringIReadOnlyDict)["key"]);
+            IEnumerator enumerator = ((IDictionary)MyStringToStringIDict).GetEnumerator();
+            enumerator.MoveNext();
+
+            DictionaryEntry entry = (DictionaryEntry)(enumerator.Current);
+            Assert.Equal("key", entry.Key);
+            Assert.Equal("value", entry.Value);
+
+            Assert.Equal("value", ((Dictionary<string, string>)MyStringToStringGenericDict)["key"]);
+            Assert.Equal("value", ((IDictionary<string, string>)MyStringToStringGenericIDict)["key"]);
+            Assert.Equal("value", ((IReadOnlyDictionary<string, string>)MyStringToStringGenericIReadOnlyDict)["key"]);
 
             Assert.Equal("value", ((ImmutableDictionary<string, string>)MyStringToStringImmutableDict)["key"]);
             Assert.Equal("value", ((IImmutableDictionary<string, string>)MyStringToStringIImmutableDict)["key"]);
             Assert.Equal("value", ((ImmutableSortedDictionary<string, string>)MyStringToStringImmutableSortedDict)["key"]);
 
+            Assert.Equal("myKey", ((KeyValuePair<string, string>)MyStringToStringKeyValuePair).Key);
+            Assert.Equal("myValue", ((KeyValuePair<string, string>)MyStringToStringKeyValuePair).Value);
+
             Assert.Equal(2, ((Stack<string>)MyStringStackT).Count);
             Assert.True(((Stack<string>)MyStringStackT).Contains("Hello"));
             Assert.True(((Stack<string>)MyStringStackT).Contains("World"));
index 3a40d7f..0c7d9e1 100644 (file)
@@ -52,6 +52,7 @@ namespace System.Text.Json.Serialization.Tests
         public ICollection<string> MyStringICollectionT { get; set; }
         public IReadOnlyCollection<string> MyStringIReadOnlyCollectionT { get; set; }
         public IReadOnlyList<string> MyStringIReadOnlyListT { get; set; }
+        public ISet<string> MyStringISetT { get; set; }
 
         public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}";
         public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}";
@@ -100,7 +101,8 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyStringIListT"" : [""Hello""]," +
                 @"""MyStringICollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," +
-                @"""MyStringIReadOnlyListT"" : [""Hello""]";
+                @"""MyStringIReadOnlyListT"" : [""Hello""]," +
+                @"""MyStringISetT"" : [""Hello""]";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
 
@@ -150,6 +152,7 @@ namespace System.Text.Json.Serialization.Tests
             MyStringICollectionT = new string[] { "Hello" };
             MyStringIReadOnlyCollectionT = new string[] { "Hello" };
             MyStringIReadOnlyListT = new string[] { "Hello" };
+            MyStringISetT = new HashSet<string> { "Hello" };
         }
 
         public void Verify()
@@ -198,6 +201,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal("Hello", MyStringICollectionT.First());
             Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First());
             Assert.Equal("Hello", MyStringIReadOnlyListT[0]);
+            Assert.Equal("Hello", MyStringISetT.First());
         }
     }
 }
index db237f8..ed9f6ba 100644 (file)
@@ -2,9 +2,9 @@
 // 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;
-using System.Linq;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -334,6 +334,149 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class TestClassWithObjectIEnumerable : ITestClass
+    {
+        public IEnumerable MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            SimpleTestClass obj1 = new SimpleTestClass();
+            obj1.Initialize();
+
+            SimpleTestClass obj2 = new SimpleTestClass();
+            obj2.Initialize();
+
+            MyData = new SimpleTestClass[] { obj1, obj2 };
+        }
+
+        public void Verify()
+        {
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                    obj.Verify();
+                }
+                else
+                {
+                    ((SimpleTestClass)data).Verify();
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
+    public class TestClassWithObjectIList : ITestClass
+    {
+        public IList MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<SimpleTestClass>();
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                MyData.Add(obj);
+            }
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                MyData.Add(obj);
+            }
+        }
+
+        public void Verify()
+        {
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                    obj.Verify();
+                }
+                else
+                {
+                    ((SimpleTestClass)data).Verify();
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
+    public class TestClassWithObjectICollection : ITestClass
+    {
+        public ICollection MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            List<SimpleTestClass> dataTemp = new List<SimpleTestClass>();
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                dataTemp.Add(obj);
+            }
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                dataTemp.Add(obj);
+            }
+
+            MyData = dataTemp;
+        }
+
+        public void Verify()
+        {
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(element.GetRawText());
+                    obj.Verify();
+                }
+                else
+                {
+                    ((SimpleTestClass)data).Verify();
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
     public class TestClassWithObjectIReadOnlyCollectionT : ITestClass
     {
         public IReadOnlyCollection<SimpleTestClass> MyData { get; set; }
@@ -399,6 +542,40 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class TestClassWithObjectISetT : ITestClass
+    {
+        public ISet<SimpleTestClass> MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            SimpleTestClass obj1 = new SimpleTestClass();
+            obj1.Initialize();
+
+            SimpleTestClass obj2 = new SimpleTestClass();
+            obj2.Initialize();
+
+            MyData = new HashSet<SimpleTestClass> { obj1, obj2 };
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(2, MyData.Count);
+
+            foreach (SimpleTestClass obj in MyData)
+            {
+                obj.Verify();
+            }
+        }
+    }
+
     public class TestClassWithStringArray : ITestClass
     {
         public string[] MyData { get; set; }
@@ -454,6 +631,138 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class TestClassWithGenericIEnumerable : ITestClass
+    {
+        public IEnumerable MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<string>
+            {
+                "Hello",
+                "World"
+            };
+
+            int count = 0;
+            foreach (string data in MyData)
+            {
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+
+        public void Verify()
+        {
+            string[] expected = { "Hello", "World" };
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    Assert.Equal(expected[count], element.GetString());
+                }
+                else
+                {
+                    Assert.Equal(expected[count], (string)data);
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
+    public class TestClassWithGenericIList : ITestClass
+    {
+        public IList MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<string>
+            {
+                "Hello",
+                "World"
+            };
+            Assert.Equal(2, MyData.Count);
+        }
+
+        public void Verify()
+        {
+            string[] expected = { "Hello", "World" };
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    Assert.Equal(expected[count], element.GetString());
+                }
+                else
+                {
+                    Assert.Equal(expected[count], (string)data);
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
+    public class TestClassWithGenericICollection : ITestClass
+    {
+        public ICollection MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<string>
+            {
+                "Hello",
+                "World"
+            };
+            Assert.Equal(2, MyData.Count);
+        }
+
+        public void Verify()
+        {
+            string[] expected = { "Hello", "World" };
+            int count = 0;
+            foreach (object data in MyData)
+            {
+                if (data is JsonElement element)
+                {
+                    Assert.Equal(expected[count], element.GetString());
+                }
+                else
+                {
+                    Assert.Equal(expected[count], (string)data);
+                }
+                count++;
+            }
+            Assert.Equal(2, count);
+        }
+    }
+
     public class TestClassWithGenericIEnumerableT : ITestClass
     {
         public IEnumerable<string> MyData { get; set; }
@@ -629,6 +938,51 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
+    public class TestClassWithGenericISetT : ITestClass
+    {
+        public ISet<string> MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new HashSet<string>
+            {
+                "Hello",
+                "World"
+            };
+            Assert.Equal(2, MyData.Count);
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(2, MyData.Count);
+
+            bool helloSeen = false;
+            bool worldSeen = false;
+
+            foreach (string data in MyData)
+            {
+                if (data == "Hello")
+                {
+                    helloSeen = true;
+                }
+                else if (data == "World")
+                {
+                    worldSeen = true;
+                }
+            }
+
+            Assert.True(helloSeen && worldSeen);
+        }
+    }
+
     public class TestClassWithStringToPrimitiveDictionary : ITestClass
     {
         public Dictionary<string, int> MyInt32Dict { get; set; }
index 8be65ec..8bd6cb7 100644 (file)
@@ -23,18 +23,26 @@ namespace System.Text.Json.Serialization.Tests
                 yield return new object[] { typeof(TestClassWithNestedObjectInner), TestClassWithNestedObjectInner.s_data };
                 yield return new object[] { typeof(TestClassWithNestedObjectOuter), TestClassWithNestedObjectOuter.s_data };
                 yield return new object[] { typeof(TestClassWithObjectArray), TestClassWithObjectArray.s_data };
+                yield return new object[] { typeof(TestClassWithObjectIEnumerable), TestClassWithObjectIEnumerable.s_data };
+                yield return new object[] { typeof(TestClassWithObjectIList), TestClassWithObjectIList.s_data };
+                yield return new object[] { typeof(TestClassWithObjectICollection), TestClassWithObjectICollection.s_data };
                 yield return new object[] { typeof(TestClassWithObjectIEnumerableT), TestClassWithObjectIEnumerableT.s_data };
                 yield return new object[] { typeof(TestClassWithObjectIListT), TestClassWithObjectIListT.s_data };
                 yield return new object[] { typeof(TestClassWithObjectICollectionT), TestClassWithObjectICollectionT.s_data };
                 yield return new object[] { typeof(TestClassWithObjectIReadOnlyCollectionT), TestClassWithObjectIReadOnlyCollectionT.s_data };
                 yield return new object[] { typeof(TestClassWithObjectIReadOnlyListT), TestClassWithObjectIReadOnlyListT.s_data };
+                yield return new object[] { typeof(TestClassWithObjectISetT), TestClassWithObjectISetT.s_data };
                 yield return new object[] { typeof(TestClassWithStringArray), TestClassWithStringArray.s_data };
                 yield return new object[] { typeof(TestClassWithGenericList), TestClassWithGenericList.s_data };
+                yield return new object[] { typeof(TestClassWithGenericIEnumerable), TestClassWithGenericIEnumerable.s_data };
+                yield return new object[] { typeof(TestClassWithGenericIList), TestClassWithGenericIList.s_data };
+                yield return new object[] { typeof(TestClassWithGenericICollection), TestClassWithGenericICollection.s_data };
                 yield return new object[] { typeof(TestClassWithGenericIEnumerableT), TestClassWithGenericIEnumerableT.s_data };
                 yield return new object[] { typeof(TestClassWithGenericIListT), TestClassWithGenericIListT.s_data };
                 yield return new object[] { typeof(TestClassWithGenericICollectionT), TestClassWithGenericICollectionT.s_data };
                 yield return new object[] { typeof(TestClassWithGenericIReadOnlyCollectionT), TestClassWithGenericIReadOnlyCollectionT.s_data };
                 yield return new object[] { typeof(TestClassWithGenericIReadOnlyListT), TestClassWithGenericIReadOnlyListT.s_data };
+                yield return new object[] { typeof(TestClassWithGenericISetT), TestClassWithGenericISetT.s_data };
                 yield return new object[] { typeof(TestClassWithStringToPrimitiveDictionary), TestClassWithStringToPrimitiveDictionary.s_data };
                 yield return new object[] { typeof(TestClassWithObjectIEnumerableConstructibleTypes), TestClassWithObjectIEnumerableConstructibleTypes.s_data };
                 yield return new object[] { typeof(TestClassWithObjectImmutableTypes), TestClassWithObjectImmutableTypes.s_data };
@@ -58,18 +66,26 @@ namespace System.Text.Json.Serialization.Tests
                 yield return new object[] { new TestClassWithNestedObjectInner() };
                 yield return new object[] { new TestClassWithNestedObjectOuter() };
                 yield return new object[] { new TestClassWithObjectArray() };
+                yield return new object[] { new TestClassWithObjectIEnumerable() };
+                yield return new object[] { new TestClassWithObjectIList() };
+                yield return new object[] { new TestClassWithObjectICollection() };
                 yield return new object[] { new TestClassWithObjectIEnumerableT() };
                 yield return new object[] { new TestClassWithObjectIListT() };
                 yield return new object[] { new TestClassWithObjectICollectionT() };
                 yield return new object[] { new TestClassWithObjectIReadOnlyCollectionT() };
                 yield return new object[] { new TestClassWithObjectIReadOnlyListT() };
+                yield return new object[] { new TestClassWithObjectISetT() };
                 yield return new object[] { new TestClassWithStringArray() };
                 yield return new object[] { new TestClassWithGenericList() };
+                yield return new object[] { new TestClassWithGenericIEnumerable() };
+                yield return new object[] { new TestClassWithGenericIList() };
+                yield return new object[] { new TestClassWithGenericICollection() };
                 yield return new object[] { new TestClassWithGenericIEnumerableT() };
                 yield return new object[] { new TestClassWithGenericIListT() };
                 yield return new object[] { new TestClassWithGenericICollectionT() };
                 yield return new object[] { new TestClassWithGenericIReadOnlyCollectionT() };
                 yield return new object[] { new TestClassWithGenericIReadOnlyListT() };
+                yield return new object[] { new TestClassWithGenericISetT() };
                 yield return new object[] { new TestClassWithStringToPrimitiveDictionary() };
                 yield return new object[] { new TestClassWithObjectIEnumerableConstructibleTypes() };
                 yield return new object[] { new TestClassWithObjectImmutableTypes() };
index ca26eeb..41dffa1 100644 (file)
@@ -355,6 +355,94 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadISetTOfISetT_Throws()
+        {
+            ISet<ISet<int>> result = JsonSerializer.Parse<ISet<ISet<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            if (result.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.Last());
+            }
+        }
+
+        [Fact]
+        public static void ReadISetTOfHashSetT()
+        {
+            ISet<HashSet<int>> result = JsonSerializer.Parse<ISet<HashSet<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            if (result.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.Last());
+            }
+        }
+
+        [Fact]
+        public static void ReadHashSetTOfISetT()
+        {
+            HashSet<ISet<int>> result = JsonSerializer.Parse<HashSet<ISet<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            if (result.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.Last());
+            }
+        }
+
+        [Fact]
+        public static void ReadISetTOfArray()
+        {
+            ISet<int[]> result = JsonSerializer.Parse<ISet<int[]>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            if (result.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, result.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, result.Last());
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfISetT()
+        {
+            ISet<int>[] result = JsonSerializer.Parse<ISet<int>[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            Assert.Equal(new HashSet<int> { 1, 2 }, result.First());
+            Assert.Equal(new HashSet<int> { 3, 4 }, result.Last());
+        }
+
+        [Fact]
+        public static void ReadPrimitiveISetT()
+        {
+            ISet<int> result = JsonSerializer.Parse<ISet<int>>(Encoding.UTF8.GetBytes(@"[1,2]"));
+
+            Assert.Equal(new HashSet<int> { 1, 2 }, result);
+
+            result = JsonSerializer.Parse<ISet<int>>(Encoding.UTF8.GetBytes(@"[]"));
+            Assert.Equal(0, result.Count());
+        }
+
+        [Fact]
         public static void StackTOfStackT()
         {
             Stack<Stack<int>> result = JsonSerializer.Parse<Stack<Stack<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
@@ -627,5 +715,75 @@ namespace System.Text.Json.Serialization.Tests
             result = JsonSerializer.Parse<SortedSet<int>>(Encoding.UTF8.GetBytes(@"[]"));
             Assert.Equal(0, result.Count());
         }
+
+        [Fact]
+        public static void ReadPrimitiveKeyValuePair()
+        {
+            KeyValuePair<string, int> input = JsonSerializer.Parse<KeyValuePair<string, int>>(@"{""Key"": 123}");
+
+            Assert.Equal(input.Key, "Key");
+            Assert.Equal(input.Value, 123);
+
+            input = JsonSerializer.Parse<KeyValuePair<string, int>>(@"{""Key"": ""Key"", ""Value"": 123}");
+
+            Assert.Equal(input.Key, "Key");
+            Assert.Equal(input.Value, 123);
+
+            // Invalid form: extra property
+            Assert.Throws<JsonException>(() => JsonSerializer.Parse<KeyValuePair<string, int>>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}"));
+
+            // Invalid form: does not contain both Key and Value properties
+            Assert.Throws<JsonException>(() => JsonSerializer.Parse<KeyValuePair<string, int>>(@"{""Key"": ""Key"", ""Val"": 123"));
+        }
+
+        [Fact]
+        public static void ReadListOfKeyValuePair()
+        {
+            List<KeyValuePair<string, int>> input = JsonSerializer.Parse<List<KeyValuePair<string, int>>>(@"[{""123"":123},{""456"": 456}]");
+
+            Assert.Equal(2, input.Count);
+            Assert.Equal("123", input[0].Key);
+            Assert.Equal(123, input[0].Value);
+            Assert.Equal("456", input[1].Key);
+            Assert.Equal(456, input[1].Value);
+
+            input = JsonSerializer.Parse<List<KeyValuePair<string, int>>>(@"[{""Key"":""123"",""Value"": 123},{""Key"": ""456"",""Value"": 456}]");
+
+            Assert.Equal(2, input.Count);
+            Assert.Equal("123", input[0].Key);
+            Assert.Equal(123, input[0].Value);
+            Assert.Equal("456", input[1].Key);
+            Assert.Equal(456, input[1].Value);
+        }
+
+        [Fact]
+        public static void ReadKeyValuePairOfList()
+        {
+            KeyValuePair<string, List<int>> input = JsonSerializer.Parse<KeyValuePair<string, List<int>>>(@"{""Key"":[1, 2, 3]}");
+
+            Assert.Equal("Key", input.Key);
+            Assert.Equal(3, input.Value.Count);
+            Assert.Equal(1, input.Value[0]);
+            Assert.Equal(2, input.Value[1]);
+            Assert.Equal(3, input.Value[2]);
+
+            input = JsonSerializer.Parse<KeyValuePair<string, List<int>>>(@"{""Key"": ""Key"", ""Value"": [1, 2, 3]}");
+
+            Assert.Equal("Key", input.Key);
+            Assert.Equal(3, input.Value.Count);
+            Assert.Equal(1, input.Value[0]);
+            Assert.Equal(2, input.Value[1]);
+            Assert.Equal(3, input.Value[2]);
+        }
+
+        [Fact]
+        public static void ReadKeyValuePairOfKeyValuePair()
+        {
+            KeyValuePair<string, KeyValuePair<string, int>> input = JsonSerializer.Parse<KeyValuePair<string, KeyValuePair<string, int>>>(@"{""Key"":{""Key"":1}}");
+
+            Assert.Equal("Key", input.Key);
+            Assert.Equal("Key", input.Value.Key);
+            Assert.Equal(1, input.Value.Value);
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs
new file mode 100644 (file)
index 0000000..ee0e2cc
--- /dev/null
@@ -0,0 +1,398 @@
+// 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.Linq;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        public static void ReadGenericIEnumerableOfIEnumerable()
+        {
+            IEnumerable<IEnumerable> result = JsonSerializer.Parse<IEnumerable<IEnumerable>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (IEnumerable ie in result)
+            {
+                foreach (JsonElement i in ie)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadIEnumerableOfArray()
+        {
+            IEnumerable result = JsonSerializer.Parse<IEnumerable>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfIEnumerable()
+        {
+            IEnumerable[] result = JsonSerializer.Parse<IEnumerable[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (IEnumerable arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveIEnumerable()
+        {
+            IEnumerable result = JsonSerializer.Parse<IEnumerable>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<IEnumerable>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+
+        public static void ReadGenericIListOfIList()
+        {
+            IList<IList> result = JsonSerializer.Parse<IList<IList>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (IList list in result)
+            {
+                foreach (JsonElement i in list)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadIListOfArray()
+        {
+            IList result = JsonSerializer.Parse<IList>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfIList()
+        {
+            IList[] result = JsonSerializer.Parse<IList[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (IList arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveIList()
+        {
+            IList result = JsonSerializer.Parse<IList>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<IList>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+
+        public static void ReadGenericICollectionOfICollection()
+        {
+            ICollection<ICollection> result = JsonSerializer.Parse<ICollection<ICollection>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (ICollection ie in result)
+            {
+                foreach (JsonElement i in ie)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadICollectionOfArray()
+        {
+            ICollection result = JsonSerializer.Parse<ICollection>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfICollection()
+        {
+            ICollection[] result = JsonSerializer.Parse<ICollection[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (ICollection arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveICollection()
+        {
+            ICollection result = JsonSerializer.Parse<ICollection>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<ICollection>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+
+        public static void ReadGenericStackOfStack()
+        {
+            Stack<Stack> result = JsonSerializer.Parse<Stack<Stack>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 4;
+
+            foreach (Stack stack in result)
+            {
+                foreach (JsonElement i in stack)
+                {
+                    Assert.Equal(expected--, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadStackOfArray()
+        {
+            Stack result = JsonSerializer.Parse<Stack>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 3;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+                expected = 1;
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfStack()
+        {
+            Stack[] result = JsonSerializer.Parse<Stack[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 2;
+
+            foreach (Stack arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected--, i.GetInt32());
+                }
+                expected = 4;
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveStack()
+        {
+            Stack result = JsonSerializer.Parse<Stack>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 2;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected--, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<Stack>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+
+        public static void ReadGenericQueueOfQueue()
+        {
+            Queue<Queue> result = JsonSerializer.Parse<Queue<Queue>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (Queue ie in result)
+            {
+                foreach (JsonElement i in ie)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadQueueOfArray()
+        {
+            Queue result = JsonSerializer.Parse<Queue>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfQueue()
+        {
+            Queue[] result = JsonSerializer.Parse<Queue[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (Queue arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveQueue()
+        {
+            Queue result = JsonSerializer.Parse<Queue>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<Queue>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+
+        [Fact]
+        public static void ReadArrayListOfArray()
+        {
+            ArrayList result = JsonSerializer.Parse<ArrayList>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (JsonElement arr in result)
+            {
+                foreach (JsonElement i in arr.EnumerateArray())
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadArrayOfArrayList()
+        {
+            ArrayList[] result = JsonSerializer.Parse<ArrayList[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (ArrayList arr in result)
+            {
+                foreach (JsonElement i in arr)
+                {
+                    Assert.Equal(expected++, i.GetInt32());
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadPrimitiveArrayList()
+        {
+            ArrayList result = JsonSerializer.Parse<ArrayList>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (JsonElement i in result)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
+
+            result = JsonSerializer.Parse<ArrayList>(Encoding.UTF8.GetBytes(@"[]"));
+
+            int count = 0;
+            IEnumerator e = result.GetEnumerator();
+            while (e.MoveNext())
+            {
+                count++;
+            }
+            Assert.Equal(0, count);
+        }
+    }
+}
index 8128e44..e8a8818 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Linq;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -286,6 +287,122 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void WriteISetTOfISetT()
+        {
+            ISet<ISet<int>> input = new HashSet<ISet<int>>
+            {
+                new HashSet<int>() { 1, 2 },
+                new HashSet<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<ISet<ISet<int>>>(json);
+
+            if (input.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.Last());
+            }
+        }
+
+        [Fact]
+        public static void WriteISetTOfHashSetT()
+        {
+            ISet<HashSet<int>> input = new HashSet<HashSet<int>>
+            {
+                new HashSet<int>() { 1, 2 },
+                new HashSet<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<ISet<HashSet<int>>>(json);
+
+            if (input.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.Last());
+            }
+        }
+
+        [Fact]
+        public static void WriteHashSetTOfISetT()
+        {
+            HashSet<ISet<int>> input = new HashSet<ISet<int>>
+            {
+                new HashSet<int>() { 1, 2 },
+                new HashSet<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<HashSet<ISet<int>>>(json);
+
+            if (input.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.Last());
+            }
+        }
+
+        [Fact]
+        public static void WriteISetTOfArray()
+        {
+            ISet<int[]> input = new HashSet<int[]>
+            {
+                new int[] { 1, 2 },
+                new int[] { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.True(json.Contains("[1,2]"));
+            Assert.True(json.Contains("[3,4]"));
+        }
+
+        [Fact]
+        public static void WriteArrayOfISetT()
+        {
+            ISet<int>[] input = new HashSet<int>[2];
+            input[0] = new HashSet<int>() { 1, 2 };
+            input[1] = new HashSet<int>() { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<ISet<int>[]>(json);
+            Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+            Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
+        }
+
+        [Fact]
+        public static void WritePrimitiveISetT()
+        {
+            ISet<int> input = new HashSet<int> { 1, 2 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.True(json == "[1,2]" || json == "[2,1]");
+        }
+
+        [Fact]
         public static void WriteStackTOfStackT()
         {
             Stack<Stack<int>> input = new Stack<Stack<int>>(new List<Stack<int>>
@@ -387,7 +504,20 @@ namespace System.Text.Json.Serialization.Tests
             });
 
             string json = JsonSerializer.ToString(input);
-            Assert.Equal("[[1,2],[3,4]]", json);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<HashSet<HashSet<int>>>(json);
+
+            if (input.First().Contains(1))
+            {
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
+            }
+            else
+            {
+                Assert.Equal(new HashSet<int> { 3, 4 }, input.First());
+                Assert.Equal(new HashSet<int> { 1, 2 }, input.Last());
+            }
         }
 
         [Fact]
@@ -400,7 +530,8 @@ namespace System.Text.Json.Serialization.Tests
             });
 
             string json = JsonSerializer.ToString(input);
-            Assert.Equal("[[1,2],[3,4]]", json);
+            Assert.True(json.Contains("[1,2]"));
+            Assert.True(json.Contains("[3,4]"));
         }
 
         [Fact]
@@ -411,7 +542,11 @@ namespace System.Text.Json.Serialization.Tests
             input[1] = new HashSet<int>(new List<int> { 3, 4 });
 
             string json = JsonSerializer.ToString(input);
-            Assert.Equal("[[1,2],[3,4]]", json);
+
+            // Because order isn't guaranteed, roundtrip data to ensure write was accurate.
+            input = JsonSerializer.Parse<HashSet<int>[]>(json);
+            Assert.Equal(new HashSet<int> { 1, 2 }, input.First());
+            Assert.Equal(new HashSet<int> { 3, 4 }, input.Last());
         }
 
         [Fact]
@@ -420,7 +555,7 @@ namespace System.Text.Json.Serialization.Tests
             HashSet<int> input = new HashSet<int>(new List<int> { 1, 2 });
 
             string json = JsonSerializer.ToString(input);
-            Assert.Equal("[1,2]", json);
+            Assert.True(json == "[1,2]" || json == "[2,1]");
         }
 
         [Fact]
@@ -488,5 +623,46 @@ namespace System.Text.Json.Serialization.Tests
             string json = JsonSerializer.ToString(input);
             Assert.Equal("[1,2]", json);
         }
+
+        [Fact]
+        public static void WritePrimitiveKeyValuePair()
+        {
+            KeyValuePair<string, int> input = new KeyValuePair<string, int>("Key", 123) ;
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal(@"{""Key"":""Key"",""Value"":123}", json);
+        }
+
+        [Fact]
+        public static void WriteListOfKeyValuePair()
+        {
+            List<KeyValuePair<string, int>> input = new List<KeyValuePair<string, int>>
+            {
+                new KeyValuePair<string, int>("123", 123),
+                new KeyValuePair<string, int>("456", 456)
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal(@"[{""Key"":""123"",""Value"":123},{""Key"":""456"",""Value"":456}]", json);
+        }
+
+        [Fact]
+        public static void WriteKeyValuePairOfList()
+        {
+            KeyValuePair<string, List<int>> input = new KeyValuePair<string, List<int>>("Key", new List<int> { 1, 2, 3 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal(@"{""Key"":""Key"",""Value"":[1,2,3]}", json);
+        }
+
+        [Fact]
+        public static void WriteKeyValuePairOfKeyValuePair()
+        {
+            KeyValuePair<string, KeyValuePair<string, int>> input = new KeyValuePair<string, KeyValuePair<string, int>>(
+                "Key", new KeyValuePair<string, int>("Key", 1));
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal(@"{""Key"":""Key"",""Value"":{""Key"":""Key"",""Value"":1}}", json);
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs
new file mode 100644 (file)
index 0000000..1bcd0d5
--- /dev/null
@@ -0,0 +1,263 @@
+// 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 Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        [Fact]
+        public static void WriteIEnumerableOfIEnumerable()
+        {
+            IEnumerable input = new List<List<int>>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        public static void WriteGenericIEnumerableOfIEnumerable()
+        {
+            IEnumerable<IEnumerable> input = new List<IEnumerable>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfIEnumerable()
+        {
+            IEnumerable[] input = new IEnumerable[2];
+            input[0] = new List<int>() { 1, 2 };
+            input[1] = new List<int>() { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveIEnumerable()
+        {
+            IEnumerable input = new List<int> { 1, 2 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
+        public static void WriteIListOfIList()
+        {
+            IList input = new List<IList>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        public static void WriteIListGenericOfIList()
+        {
+            IList<IList> input = new List<IList>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfIList()
+        {
+            IList[] input = new IList[2];
+            input[0] = new List<int>() { 1, 2 };
+            input[1] = new List<int>() { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveIList()
+        {
+            IList input = new List<int> { 1, 2 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
+        public static void WriteICollectionOfICollection()
+        {
+            ICollection input = new List<ICollection>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        public static void WriteGenericICollectionOfICollection()
+        {
+            ICollection<ICollection> input = new List<ICollection>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfICollection()
+        {
+            ICollection[] input = new List<int>[2];
+            input[0] = new List<int>() { 1, 2 };
+            input[1] = new List<int>() { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveICollection()
+        {
+            ICollection input = new List<int> { 1, 2 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
+        public static void WriteStackOfStack()
+        {
+            Stack input = new Stack();
+            input.Push(new Stack(new List<int>() { 1, 2 }));
+            input.Push(new Stack(new List<int>() { 3, 4 }));
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[4,3],[2,1]]", json);
+        }
+
+        public static void WriteGenericStackOfStack()
+        {
+            Stack<Stack> input = new Stack<Stack>();
+            input.Push(new Stack(new List<int>() { 1, 2 }));
+            input.Push(new Stack(new List<int>() { 3, 4 }));
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[4,3],[2,1]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfStack()
+        {
+            Stack[] input = new Stack[2];
+            input[0] = new Stack(new List<int>() { 1, 2 });
+            input[1] = new Stack(new List<int>() { 3, 4 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[2,1],[4,3]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveStack()
+        {
+            Stack input = new Stack( new List<int> { 1, 2 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[2,1]", json);
+        }
+
+        [Fact]
+        public static void WriteQueueOfQueue()
+        {
+            Queue input = new Queue();
+            input.Enqueue(new Queue(new List<int>() { 1, 2 }));
+            input.Enqueue(new Queue(new List<int>() { 3, 4 }));
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        public static void WriteGenericQueueOfQueue()
+        {
+            Queue<Queue> input = new Queue<Queue>();
+            input.Enqueue(new Queue(new List<int>() { 1, 2 }));
+            input.Enqueue(new Queue(new List<int>() { 3, 4 }));
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfQueue()
+        {
+            Queue[] input = new Queue[2];
+            input[0] = new Queue(new List<int>() { 1, 2 });
+            input[1] = new Queue(new List<int>() { 3, 4 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveQueue()
+        {
+            Queue input = new Queue(new List<int> { 1, 2 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayListOfArrayList()
+        {
+            ArrayList input = new ArrayList
+            {
+                new ArrayList(new List<int>() { 1, 2 }),
+                new ArrayList(new List<int>() { 3, 4 })
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfArrayList()
+        {
+            ArrayList[] input = new ArrayList[2];
+            input[0] = new ArrayList(new List<int>() { 1, 2 });
+            input[1] = new ArrayList(new List<int>() { 3, 4 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveArrayList()
+        {
+            ArrayList input = new ArrayList(new List<int> { 1, 2 });
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+    }
+}
index c84d8c5..089f72c 100644 (file)
     <Compile Include="Serialization\Value.ReadTests.cs" />
     <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.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="TestCaseType.cs" />
     <Compile Include="TestClasses.ClassWithComplexObjects.cs" />
     <Compile Include="Utf8JsonReaderTests.cs" />