Populate non-immutable collections directly on deserialize (dotnet/corefx#41482)
authorLayomi Akinrinade <laakinri@microsoft.com>
Sat, 19 Oct 2019 00:31:06 +0000 (20:31 -0400)
committerGitHub <noreply@github.com>
Sat, 19 Oct 2019 00:31:06 +0000 (20:31 -0400)
* Populate non-immutable collections directly on deserialize

(rather than storing in temporary collections and using converters to
create and populate instances)

* Fix deserilizing nested dictionaries

* Don't get add method for types we can populate without reflection; fix failing tests

* Misc perf changes

* Additional changes

* More changes

* Address review feedback

* Address feedback

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

43 files changed:
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/DefaultDerivedDictionaryConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.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/JsonPropertyInfoNotNullableContravariant.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.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.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/MemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.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/tests/NewtonsoftTests/DateTimeConverterTests.cs
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.DerivedTypes.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.NonGenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs

index 56c5936..5eec31c 100644 (file)
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
     <Compile Include="System\Text\Json\Serialization\ClassType.cs" />
     <Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\DefaultDerivedDictionaryConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\DefaultDerivedEnumerableConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\DefaultArrayConverter.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\EnumConverterOptions.cs" />
@@ -87,7 +83,6 @@
     <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonClassInfo.Helpers.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterFactory.cs" />
index 843d370..e4dad76 100644 (file)
@@ -23,8 +23,5 @@ namespace System.Text.Json
         Enumerable = 0x8,
         // IDictionary
         Dictionary = 0x10,
-        // Is deserialized by passing a IDictionary to its constructor
-        // i.e. immutable dictionaries, Hashtable, SortedList,
-        IDictionaryConstructible = 0x20,
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs
deleted file mode 100644 (file)
index f70199c..0000000
+++ /dev/null
@@ -1,18 +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;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverter
-    {
-        public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
-        {
-            JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo;
-            JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options);
-            return elementPropertyInfo.CreateDerivedDictionaryInstance(ref state, collectionPropertyInfo, sourceDictionary);
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs
deleted file mode 100644 (file)
index 21a0510..0000000
+++ /dev/null
@@ -1,18 +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;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter
-    {
-        public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
-        {
-            JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo;
-            JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options);
-            return elementPropertyInfo.CreateDerivedEnumerableInstance(ref state, collectionPropertyInfo, sourceList);
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs
deleted file mode 100644 (file)
index 46b6bb3..0000000
+++ /dev/null
@@ -1,19 +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;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    internal sealed class DefaultICollectionConverter : JsonEnumerableConverter
-    {
-        public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
-        {
-            Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            Type elementType = state.Current.JsonPropertyInfo.ElementType;
-            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
-            return propertyInfo.CreateIEnumerableInstance(ref state, enumerableType, sourceList);
-        }
-    }
-}
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
deleted file mode 100644 (file)
index 2ad5acd..0000000
+++ /dev/null
@@ -1,19 +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;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter
-    {
-        public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
-        {
-            Type dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            Type elementType = state.Current.JsonPropertyInfo.ElementType;
-            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
-            return propertyInfo.CreateIDictionaryInstance(ref state, dictionaryType, sourceDictionary);
-        }
-    }
-}
index 357a968..3ec77b5 100644 (file)
@@ -59,11 +59,14 @@ namespace System.Text.Json.Serialization.Converters
         public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
         {
             Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            Type elementType = state.Current.GetElementType();
+
+            JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+            Type elementType = elementClassInfo.Type;
 
             string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _);
 
-            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
+            JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options);
+            Debug.Assert(propertyInfo != null);
             return propertyInfo.CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, sourceDictionary, options);
         }
     }
index b774ea9..296d542 100644 (file)
@@ -102,14 +102,47 @@ namespace System.Text.Json.Serialization.Converters
             options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
         }
 
+        public static bool IsImmutableEnumerable(Type type, out bool IsImmutableArray)
+        {
+            if (!type.IsGenericType)
+            {
+                IsImmutableArray = false;
+                return false;
+            }
+
+            switch (type.GetGenericTypeDefinition().FullName)
+            {
+                case ImmutableArrayGenericTypeName:
+                    IsImmutableArray = true;
+                    return true;
+                case ImmutableListGenericTypeName:
+                case ImmutableListGenericInterfaceTypeName:
+                case ImmutableStackGenericTypeName:
+                case ImmutableStackGenericInterfaceTypeName:
+                case ImmutableQueueGenericTypeName:
+                case ImmutableQueueGenericInterfaceTypeName:
+                case ImmutableSortedSetGenericTypeName:
+                case ImmutableHashSetGenericTypeName:
+                case ImmutableSetGenericInterfaceTypeName:
+                    IsImmutableArray = false;
+                    return true;
+                default:
+                    IsImmutableArray = false;
+                    return false;
+            }
+        }
+
         public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
         {
             Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
-            Type elementType = state.Current.GetElementType();
+
+            JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+            Type elementType = elementClassInfo.Type;
 
             string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
 
-            JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
+            JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options);
+            Debug.Assert(propertyInfo != null);
             return propertyInfo.CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, sourceList, options);
         }
     }
index 66fa353..4ba60eb 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using System.Text.Json.Serialization;
@@ -11,131 +10,64 @@ namespace System.Text.Json
 {
     internal partial class JsonClassInfo
     {
-        private void AddPolicyProperty(Type propertyType, JsonSerializerOptions options)
+        private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
         {
-            // A policy property is not a real property on a type; instead it leverages the existing converter
-            // logic and generic support to avoid boxing. It is used with values types and elements from collections and
-            // dictionaries. Typically it would represent a CLR type such as System.String.
-            PolicyProperty = AddProperty(
-                propertyType,
-                propertyInfo: null,        // Not a real property so this is null.
-                classType: typeof(object), // A dummy type (not used).
-                options: options);
-        }
-
-        private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
-        {
-            JsonPropertyInfo jsonInfo;
-
-            // Get implemented type, if applicable.
-            // Will return the propertyType itself if it's a non-enumerable, string, natively supported collection,
-            // or if a custom converter has been provided for the type.
-            Type implementedType = GetImplementedCollectionType(classType, propertyType, propertyInfo, out JsonConverter converter, options);
-
-            if (implementedType != propertyType)
-            {
-                jsonInfo = CreateProperty(implementedType, implementedType, implementedType, propertyInfo, typeof(object), converter, options);
-            }
-            else
-            {
-                jsonInfo = CreateProperty(propertyType, propertyType, propertyType, propertyInfo, classType, converter, options);
-            }
-
-            // Convert non-immutable dictionary interfaces to concrete types.
-            if (IsNativelySupportedCollection(propertyType) && implementedType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary)
+            bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
+            if (hasIgnoreAttribute)
             {
-                JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options);
-
-                Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType();
-                if (implementedType != newPropertyType)
-                {
-                    jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options);
-                }
-                else
-                {
-                    jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
-                }
+                return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
             }
-            else if (jsonInfo.ClassType == ClassType.Enumerable &&
-                !implementedType.IsArray &&
-                ((IsDeserializedByAssigningFromList(implementedType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedType)))
-            {
-                JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options);
 
-                // Get a runtime type for the implemented 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(implementedType);
-                if ((implementedType != newPropertyType) && implementedType.IsAssignableFrom(newPropertyType))
-                {
-                    jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options);
-                }
-                else
-                {
-                    jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
-                }
-            }
-            else if (propertyType != implementedType)
-            {
-                jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
-            }
+            ClassType classType = GetClassType(
+                propertyType,
+                parentClassType,
+                propertyInfo,
+                out Type runtimeType,
+                out Type elementType,
+                out Type nullableUnderlyingType,
+                out _,
+                out JsonConverter converter,
+                checkForAddMethod: false,
+                options);
 
-            return jsonInfo;
+            return CreateProperty(
+                declaredPropertyType: propertyType,
+                runtimePropertyType: runtimeType,
+                propertyInfo,
+                parentClassType,
+                collectionElementType: elementType,
+                nullableUnderlyingType,
+                converter,
+                classType,
+                options);
         }
 
         internal static JsonPropertyInfo CreateProperty(
             Type declaredPropertyType,
             Type runtimePropertyType,
-            Type implementedPropertyType,
             PropertyInfo propertyInfo,
             Type parentClassType,
+            Type collectionElementType,
+            Type nullableUnderlyingType,
             JsonConverter converter,
+            ClassType classType,
             JsonSerializerOptions options)
         {
-            bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
-            if (hasIgnoreAttribute)
-            {
-                return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
-            }
-
-            // Obtain the custom converter for the property.
-            if (converter == null)
-            {
-                converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo);
-            }
+            bool treatAsNullable = nullableUnderlyingType != null;
 
             // Obtain the type of the JsonPropertyInfo class to construct.
             Type propertyInfoClassType;
-            if (runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+
+            if (treatAsNullable && converter != null)
             {
-                if (converter != null)
-                {
-                    propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType(
-                        parentClassType,
-                        declaredPropertyType,
-                        runtimePropertyType,
-                        runtimePropertyType);
-                }
-                else
-                {
-                    // Attempt to find converter for underlying type.
-                    Type typeToConvert = Nullable.GetUnderlyingType(runtimePropertyType);
-                    converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo);
-                    propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert);
-                }
+                propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, nullableUnderlyingType);
             }
             else
             {
                 Type typeToConvert = converter?.TypeToConvert;
                 if (typeToConvert == null)
                 {
-                    if (IsNativelySupportedCollection(declaredPropertyType))
-                    {
-                        typeToConvert = implementedPropertyType;
-                    }
-                    else
-                    {
-                        typeToConvert = declaredPropertyType;
-                    }
+                    typeToConvert = declaredPropertyType;
                 }
 
                 // For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty".
@@ -168,30 +100,15 @@ namespace System.Text.Json
                 args: null,
                 culture: null);
 
-            // Obtain the collection element type.
-            Type collectionElementType = null;
-            if (converter == null)
-            {
-                switch (GetClassType(runtimePropertyType, options))
-                {
-                    case ClassType.Enumerable:
-                    case ClassType.Dictionary:
-                    case ClassType.IDictionaryConstructible:
-                    case ClassType.Unknown:
-                        collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo, options);
-                        break;
-                }
-            }
-
-            // Initialize the JsonPropertyInfo.
             jsonPropertyInfo.Initialize(
                 parentClassType,
                 declaredPropertyType,
                 runtimePropertyType,
-                implementedPropertyType,
+                runtimeClassType: classType,
                 propertyInfo,
                 collectionElementType,
                 converter,
+                treatAsNullable,
                 options);
 
             return jsonPropertyInfo;
@@ -199,24 +116,43 @@ namespace System.Text.Json
 
         internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options)
         {
+            JsonConverter converter = options.DetermineConverterForProperty(Type, Type, propertyInfo: null);
+
             return CreateProperty(
                 declaredPropertyType: Type,
                 runtimePropertyType: Type,
-                implementedPropertyType: Type,
                 propertyInfo: null,
                 parentClassType: Type,
-                converter: null,
-                options: options);
+                ElementType,
+                Nullable.GetUnderlyingType(Type),
+                converter,
+                ClassType,
+                options);
         }
 
         internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options)
         {
+            ClassType classType = GetClassType(
+                runtimePropertyType,
+                Type,
+                property.PropertyInfo,
+                out _,
+                out Type elementType,
+                out Type nullableType,
+                out _,
+                out JsonConverter converter,
+                checkForAddMethod: false,
+                options);
+
             JsonPropertyInfo runtimeProperty = CreateProperty(
-                property.DeclaredPropertyType, runtimePropertyType,
-                property.ImplementedPropertyType,
+                property.DeclaredPropertyType,
+                runtimePropertyType,
                 property.PropertyInfo,
                 parentClassType: Type,
-                converter: null,
+                collectionElementType: elementType,
+                nullableType,
+                converter,
+                classType,
                 options: options);
             property.CopyRuntimeSettingsTo(runtimeProperty);
 
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs
deleted file mode 100644 (file)
index ff0e38f..0000000
+++ /dev/null
@@ -1,368 +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.Diagnostics;
-using System.Reflection;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Converters;
-
-namespace System.Text.Json
-{
-    internal partial class JsonClassInfo
-    {
-        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 ListInterfaceTypeName = "System.Collections.IList";
-        private const string ListGenericInterfaceTypeName = "System.Collections.Generic.IList`1";
-        private const string ListGenericTypeName = "System.Collections.Generic.List`1";
-
-        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 StackGenericTypeName = "System.Collections.Generic.Stack`1";
-
-        public const string QueueTypeName = "System.Collections.Queue";
-        public const string QueueGenericTypeName = "System.Collections.Generic.Queue`1";
-
-        public const string SetGenericInterfaceTypeName = "System.Collections.Generic.ISet`1";
-        public const string SortedSetGenericTypeName = "System.Collections.Generic.SortedSet`1";
-        public const string HashSetGenericTypeName = "System.Collections.Generic.HashSet`1";
-
-        public const string LinkedListGenericTypeName = "System.Collections.Generic.LinkedList`1";
-
-        public const string DictionaryInterfaceTypeName = "System.Collections.IDictionary";
-        public const string DictionaryGenericTypeName = "System.Collections.Generic.Dictionary`2";
-        public const string DictionaryGenericInterfaceTypeName = "System.Collections.Generic.IDictionary`2";
-        public const string ReadOnlyDictionaryGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyDictionary`2";
-        public const string SortedDictionaryGenericTypeName = "System.Collections.Generic.SortedDictionary`2";
-        public const string KeyValuePairGenericTypeName = "System.Collections.Generic.KeyValuePair`2";
-
-        public const string ArrayListTypeName = "System.Collections.ArrayList";
-
-        // In the order we wish to detect a derived type.
-        private static readonly Type[] s_genericInterfacesWithAddMethods = new Type[]
-        {
-            typeof(IDictionary<,>),
-            typeof(ICollection<>),
-        };
-
-        // In the order we wish to detect a derived type.
-        private static readonly Type[] s_nonGenericInterfacesWithAddMethods = new Type[]
-        {
-            typeof(IDictionary),
-            typeof(IList),
-        };
-
-        // In the order we wish to detect a derived type.
-        private static readonly Type[] s_genericInterfacesWithoutAddMethods = new Type[]
-        {
-            typeof(IReadOnlyDictionary<,>),
-            typeof(IReadOnlyCollection<>),
-            typeof(IReadOnlyList<>),
-            typeof(IEnumerable<>),
-        };
-
-        // Any additional natively supported generic collection must be registered here.
-        private static readonly HashSet<string> s_nativelySupportedGenericCollections = new HashSet<string>()
-        {
-            ListGenericTypeName,
-            EnumerableGenericInterfaceTypeName,
-            ListGenericInterfaceTypeName,
-            CollectionGenericInterfaceTypeName,
-            ReadOnlyListGenericInterfaceTypeName,
-            ReadOnlyCollectionGenericInterfaceTypeName,
-            SetGenericInterfaceTypeName,
-            StackGenericTypeName,
-            QueueGenericTypeName,
-            HashSetGenericTypeName,
-            LinkedListGenericTypeName,
-            SortedSetGenericTypeName,
-            DictionaryInterfaceTypeName,
-            DictionaryGenericTypeName,
-            DictionaryGenericInterfaceTypeName,
-            ReadOnlyDictionaryGenericInterfaceTypeName,
-            SortedDictionaryGenericTypeName,
-            KeyValuePairGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableListGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableListGenericInterfaceTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableStackGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableStackGenericInterfaceTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableQueueGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableQueueGenericInterfaceTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableSortedSetTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableSortedSetGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableHashSetGenericTypeName,
-            DefaultImmutableEnumerableConverter.ImmutableSetGenericInterfaceTypeName,
-            DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericTypeName,
-            DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericInterfaceTypeName,
-            DefaultImmutableDictionaryConverter.ImmutableSortedDictionaryGenericTypeName,
-        };
-
-        // Any additional natively supported non-generic collection must be registered here.
-        private static readonly HashSet<string> s_nativelySupportedNonGenericCollections = new HashSet<string>()
-        {
-            EnumerableInterfaceTypeName,
-            CollectionInterfaceTypeName,
-            ListInterfaceTypeName,
-            DictionaryInterfaceTypeName,
-            StackTypeName,
-            QueueTypeName,
-            HashtableTypeName,
-            ArrayListTypeName,
-            SortedListTypeName,
-        };
-
-        public static Type GetImplementedCollectionType(
-            Type parentClassType,
-            Type queryType,
-            PropertyInfo propertyInfo,
-            out JsonConverter converter,
-            JsonSerializerOptions options)
-        {
-            Debug.Assert(queryType != null);
-
-            if (!(typeof(IEnumerable).IsAssignableFrom(queryType)) ||
-                queryType == typeof(string) ||
-                queryType.IsInterface ||
-                queryType.IsArray ||
-                IsNativelySupportedCollection(queryType))
-            {
-                converter = null;
-                return queryType;
-            }
-
-            // If a converter was provided, we should not detect implemented types and instead use the converter later.
-            converter = options.DetermineConverterForProperty(parentClassType, queryType, propertyInfo);
-            if (converter != null)
-            {
-                return queryType;
-            }
-
-            Type baseType = queryType.GetTypeInfo().BaseType;
-
-            // Check if the base type is a supported concrete collection.
-            if (IsNativelySupportedCollection(baseType))
-            {
-                return baseType;
-            }
-
-            // Try generic interfaces with add methods.
-            foreach (Type candidate in s_genericInterfacesWithAddMethods)
-            {
-                Type derivedGeneric = ExtractGenericInterface(queryType, candidate);
-                if (derivedGeneric != null)
-                {
-                    return derivedGeneric;
-                }
-            }
-
-            // Try non-generic interfaces with add methods.
-            foreach (Type candidate in s_nonGenericInterfacesWithAddMethods)
-            {
-                if (candidate.IsAssignableFrom(queryType))
-                {
-                    return candidate;
-                }
-            }
-
-            // Try generic interfaces without add methods
-            foreach (Type candidate in s_genericInterfacesWithoutAddMethods)
-            {
-                Type derivedGeneric = ExtractGenericInterface(queryType, candidate);
-                if (derivedGeneric != null)
-                {
-                    return derivedGeneric;
-                }
-            }
-
-            return typeof(IEnumerable);
-        }
-
-        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, JsonSerializerOptions options)
-        {
-            Type elementType = GetElementType(type, parentType: null, memberInfo: null, options);
-            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 IsNativelySupportedCollection(Type queryType)
-        {
-            Debug.Assert(queryType != null);
-
-            if (queryType.IsGenericType)
-            {
-                return s_nativelySupportedGenericCollections.Contains(queryType.GetGenericTypeDefinition().FullName);
-            }
-
-            return s_nativelySupportedNonGenericCollections.Contains(queryType.FullName);
-        }
-
-        // The following methods were copied verbatim from AspNetCore:
-        // https://github.com/aspnet/AspNetCore/blob/13ae0057fbb11fd84fcee8fca46ebc1b2d7c1e6a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs.
-
-        /// <summary>
-        /// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
-        /// created from <paramref name="interfaceType"/>.
-        /// </summary>
-        /// <param name="queryType">The <see cref="Type"/> of interest.</param>
-        /// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
-        /// <returns>
-        /// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
-        /// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
-        /// relationship.
-        /// </returns>
-        /// <remarks>
-        /// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
-        /// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
-        /// <c>typeof(KeyValuePair{string, object})</c>.
-        /// </remarks>
-        public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
-        {
-            if (queryType == null)
-            {
-                throw new ArgumentNullException(nameof(queryType));
-            }
-
-            if (interfaceType == null)
-            {
-                throw new ArgumentNullException(nameof(interfaceType));
-            }
-
-            if (IsGenericInstantiation(queryType, interfaceType))
-            {
-                // queryType matches (i.e. is a closed generic type created from) the open generic type.
-                return queryType;
-            }
-
-            // Otherwise check all interfaces the type implements for a match.
-            // - If multiple different generic instantiations exists, we want the most derived one.
-            // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic.
-            //
-            // We do this by looking at interfaces on the type, and recursing to the base type
-            // if we don't find any matches.
-            return GetGenericInstantiation(queryType, interfaceType);
-        }
-
-        private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
-        {
-            return
-                candidate.GetTypeInfo().IsGenericType &&
-                candidate.GetGenericTypeDefinition() == interfaceType;
-        }
-
-        private static Type GetGenericInstantiation(Type queryType, Type interfaceType)
-        {
-            Type bestMatch = null;
-            Type[] interfaces = queryType.GetInterfaces();
-            foreach (Type @interface in interfaces)
-            {
-                if (IsGenericInstantiation(@interface, interfaceType))
-                {
-                    if (bestMatch == null)
-                    {
-                        bestMatch = @interface;
-                    }
-                    else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0)
-                    {
-                        bestMatch = @interface;
-                    }
-                    else
-                    {
-                        // There are two matches at this level of the class hierarchy, but @interface is after
-                        // bestMatch in the sort order.
-                    }
-                }
-            }
-
-            if (bestMatch != null)
-            {
-                return bestMatch;
-            }
-
-            // BaseType will be null for object and interfaces, which means we've reached 'bottom'.
-            Type baseType = queryType?.GetTypeInfo().BaseType;
-            if (baseType == null)
-            {
-                return null;
-            }
-            else
-            {
-                return GetGenericInstantiation(baseType, interfaceType);
-            }
-        }
-    }
-}
index aefe60a..b10ed84 100644 (file)
@@ -37,7 +37,6 @@ namespace System.Text.Json
 
         public delegate object ConstructorDelegate();
         public ConstructorDelegate CreateObject { get; private set; }
-        public ConstructorDelegate CreateConcreteDictionary { get; private set; }
 
         public ClassType ClassType { get; private set; }
 
@@ -60,8 +59,7 @@ namespace System.Text.Json
                 if (_elementClassInfo == null && ElementType != null)
                 {
                     Debug.Assert(ClassType == ClassType.Enumerable ||
-                        ClassType == ClassType.Dictionary ||
-                        ClassType == ClassType.IDictionaryConstructible);
+                        ClassType == ClassType.Dictionary);
 
                     _elementClassInfo = Options.GetOrAddClass(ElementType);
                 }
@@ -116,15 +114,26 @@ namespace System.Text.Json
         {
             Type = type;
             Options = options;
-            ClassType = GetClassType(type, options);
 
-            CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+            ClassType = GetClassType(
+                type,
+                parentClassType: type,
+                propertyInfo: null,
+                out Type runtimeType,
+                out Type elementType,
+                out Type nullableUnderlyingType,
+                out MethodInfo addMethod,
+                out JsonConverter converter,
+                checkForAddMethod: true,
+                options);
 
             // Ignore properties on enumerable.
             switch (ClassType)
             {
                 case ClassType.Object:
                     {
+                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+
                         PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
                         Dictionary<string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);
@@ -189,47 +198,64 @@ namespace System.Text.Json
                 case ClassType.Dictionary:
                     {
                         // Add a single property that maps to the class type so we can have policies applied.
-                        AddPolicyProperty(type, options);
-
-                        Type objectType;
-                        if (IsNativelySupportedCollection(type))
-                        {
-                            // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
-                            objectType = PolicyProperty.RuntimePropertyType;
-                        }
-                        else
-                        {
-                            // We need to create the declared instance for types implementing natively supported collections.
-                            objectType = PolicyProperty.DeclaredPropertyType;
-                        }
-
-                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType);
-
-                        ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
+                        ElementType = elementType;
+                        AddItemToObject = addMethod;
+
+                        // A policy property is not a real property on a type; instead it leverages the existing converter
+                        // logic and generic support to avoid boxing. It is used with values types, elements from collections and
+                        // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String.
+                        PolicyProperty = CreateProperty(
+                            declaredPropertyType: type,
+                            runtimePropertyType: runtimeType,
+                            propertyInfo: null, // Not a real property so this is null.
+                            parentClassType: typeof(object),
+                            collectionElementType: elementType,
+                            nullableUnderlyingType,
+                            converter: null,
+                            ClassType,
+                            options);
+
+                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType);
                     }
                     break;
-                case ClassType.IDictionaryConstructible:
+                case ClassType.Value:
                     {
-                        // Add a single property that maps to the class type so we can have policies applied.
-                        AddPolicyProperty(type, options);
-
-                        ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
+                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
 
-                        CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor(
-                           typeof(Dictionary<,>).MakeGenericType(typeof(string), ElementType));
-
-                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType);
+                        // Add a single property that maps to the class type so we can have policies applied.
+                        //AddPolicyPropertyForValue(type, options);
+                        PolicyProperty = CreateProperty(
+                            declaredPropertyType: type,
+                            runtimePropertyType: runtimeType,
+                            propertyInfo: null, // Not a real property so this is null.
+                            parentClassType: typeof(object),
+                            collectionElementType: null,
+                            nullableUnderlyingType,
+                            converter,
+                            ClassType,
+                            options);
                     }
                     break;
-                case ClassType.Value:
-                    // Add a single property that maps to the class type so we can have policies applied.
-                    AddPolicyProperty(type, options);
-                    break;
                 case ClassType.Unknown:
-                    // Add a single property that maps to the class type so we can have policies applied.
-                    AddPolicyProperty(type, options);
-                    PropertyCache = new Dictionary<string, JsonPropertyInfo>();
-                    PropertyCacheArray = Array.Empty<JsonPropertyInfo>();
+                    {
+                        CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+
+                        // Add a single property that maps to the class type so we can have policies applied.
+                        //AddPolicyPropertyForValue(type, options);
+                        PolicyProperty = CreateProperty(
+                            declaredPropertyType: type,
+                            runtimePropertyType: runtimeType,
+                            propertyInfo: null, // Not a real property so this is null.
+                            parentClassType: typeof(object),
+                            collectionElementType: null,
+                            nullableUnderlyingType,
+                            converter,
+                            ClassType,
+                            options);
+
+                        PropertyCache = new Dictionary<string, JsonPropertyInfo>();
+                        PropertyCacheArray = Array.Empty<JsonPropertyInfo>();
+                    }
                     break;
                 default:
                     Debug.Fail($"Unexpected class type: {ClassType}");
@@ -406,6 +432,8 @@ namespace System.Text.Json
 
         public JsonPropertyInfo PolicyProperty { get; private set; }
 
+        public MethodInfo AddItemToObject { get; private set; }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, ref JsonPropertyInfo info)
         {
@@ -513,105 +541,207 @@ namespace System.Text.Json
             return key;
         }
 
-        // Return the element type of the IEnumerable or return null if not an IEnumerable.
-        public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
+        // This method gets the runtime information for a given type or property.
+        // The runtime information consists of the following:
+        // - class type,
+        // - runtime type,
+        // - element type (if the type is a collection),
+        // - the underlying type (if the type is nullable type e.g. int?),
+        // - the "add" method (if the type is a non-dictionary collection which doesn't implement IList
+        //   e.g. typeof(Stack<int>), where we retrieve the void Push(string) method), and
+        // - the converter (either native or custom), if one exists.
+        public static ClassType GetClassType(
+            Type type,
+            Type parentClassType,
+            PropertyInfo propertyInfo,
+            out Type runtimeType,
+            out Type elementType,
+            out Type nullableUnderlyingType,
+            out MethodInfo addMethod,
+            out JsonConverter converter,
+            bool checkForAddMethod,
+            JsonSerializerOptions options)
         {
-            // We want to handle as the implemented collection type, if applicable.
-            Type implementedType = GetImplementedCollectionType(parentType, propertyType, propertyInfo: null, out _, options);
+            Debug.Assert(type != null);
 
-            if (!typeof(IEnumerable).IsAssignableFrom(implementedType))
-            {
-                return null;
-            }
+            runtimeType = type;
 
-            // Check for Array.
-            Type elementType = implementedType.GetElementType();
-            if (elementType != null)
-            {
-                return elementType;
-            }
+            nullableUnderlyingType = Nullable.GetUnderlyingType(type);
 
-            // Check for Dictionary<TKey, TValue> or IEnumerable<T>
-            if (implementedType.IsGenericType)
+            // Type is nullable e.g. typeof(int?).
+            if (nullableUnderlyingType != null)
             {
-                Type[] args = implementedType.GetGenericArguments();
-                ClassType classType = GetClassType(implementedType, options);
+                // Check if there's a converter for this nullable type, e.g. do we have a converter that implements
+                // JsonConverter<int?> if the type is typeof(int?)?
+                converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo);
 
-                if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) &&
-                    args.Length >= 2 && // It is >= 2 in case there is a IDictionary<TKey, TValue, TSomeExtension>.
-                    args[0].UnderlyingSystemType == typeof(string))
+                if (converter == null)
                 {
-                    return args[1];
+                    // No converter. We'll check below if there's a converter for the non-nullable type e.g.
+                    // one that implements JsonConverter<int>, given the type is typeof(int?).
+                    type = nullableUnderlyingType;
                 }
-
-                if (classType == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
+                else
                 {
-                    return args[0];
+                    elementType = default;
+                    addMethod = default;
+                    // Don't treat the type as a Nullable when creating the property info later on, since we have a converter for it.
+                    nullableUnderlyingType = default;
+                    return ClassType.Value;
                 }
             }
 
-            if (implementedType.IsAssignableFrom(typeof(IList)) ||
-                implementedType.IsAssignableFrom(typeof(IDictionary)) ||
-                IsDeserializedByConstructingWithIList(implementedType) ||
-                IsDeserializedByConstructingWithIDictionary(implementedType))
+            converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo);
+
+            if (converter != null)
             {
-                return typeof(object);
+                elementType = default;
+                addMethod = default;
+                return type == typeof(object) ? ClassType.Unknown : ClassType.Value;
             }
 
-            throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
-        }
-
-        public static ClassType GetClassType(Type type, JsonSerializerOptions options)
-        {
-            Debug.Assert(type != null);
-
-            // We want to handle as the implemented collection type, if applicable.
-            Type implementedType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out _, options);
+            runtimeType = type;
 
-            if (implementedType.IsGenericType && implementedType.GetGenericTypeDefinition() == typeof(Nullable<>))
+            if (!(typeof(IEnumerable)).IsAssignableFrom(type))
             {
-                implementedType = Nullable.GetUnderlyingType(implementedType);
+                elementType = null;
+                addMethod = default;
+                return ClassType.Object;
             }
 
-            if (implementedType == typeof(object))
+            if (type.IsArray)
             {
-                return ClassType.Unknown;
+                elementType = type.GetElementType();
+                addMethod = default;
+                return ClassType.Enumerable;
             }
 
-            if (options.HasConverter(implementedType))
+            if (type.FullName.StartsWith("System.Collections.Generic.IEnumerable`1"))
             {
-                return ClassType.Value;
+                elementType = type.GetGenericArguments()[0];
+                runtimeType = typeof(List<>).MakeGenericType(elementType);
+                addMethod = default;
+                return ClassType.Enumerable;
+            }
+            else if (type.FullName.StartsWith("System.Collections.Generic.IDictionary`2") ||
+                type.FullName.StartsWith("System.Collections.Generic.IReadOnlyDictionary`2"))
+            {
+                Type[] genericTypes = type.GetGenericArguments();
+
+                elementType = genericTypes[1];
+                runtimeType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], elementType);
+                addMethod = default;
+                return ClassType.Dictionary;
             }
 
-            if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedType) ||
-                IsDeserializedByConstructingWithIDictionary(implementedType))
             {
-                return ClassType.IDictionaryConstructible;
+                Type genericIDictionaryType = type.GetInterface("System.Collections.Generic.IDictionary`2") ?? type.GetInterface("System.Collections.Generic.IReadOnlyDictionary`2");
+                if (genericIDictionaryType != null)
+                {
+                    Type[] genericTypes = genericIDictionaryType.GetGenericArguments();
+                    elementType = genericTypes[1];
+                    addMethod = default;
+
+                    if (type.IsInterface)
+                    {
+                        Type concreteDictionaryType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], genericTypes[1]);
+
+                        if (type.IsAssignableFrom(concreteDictionaryType))
+                        {
+                            runtimeType = concreteDictionaryType;
+                        }
+                    }
+
+                    return ClassType.Dictionary;
+                }
             }
 
-            if (typeof(IDictionary).IsAssignableFrom(implementedType) || IsDictionaryClassType(implementedType))
+            if (typeof(IDictionary).IsAssignableFrom(type))
             {
-                // Special case for immutable dictionaries
-                if (type != implementedType && !IsNativelySupportedCollection(type))
+                elementType = typeof(object);
+                addMethod = default;
+
+                if (type.IsInterface)
                 {
-                    return ClassType.IDictionaryConstructible;
+                    Type concreteDictionaryType = typeof(Dictionary<string, object>);
+
+                    if (type.IsAssignableFrom(concreteDictionaryType))
+                    {
+                        runtimeType = concreteDictionaryType;
+                    }
                 }
 
                 return ClassType.Dictionary;
             }
 
-            if (typeof(IEnumerable).IsAssignableFrom(implementedType))
             {
-                return ClassType.Enumerable;
+                Type genericIEnumerableType = type.GetInterface("System.Collections.Generic.IEnumerable`1");
+
+                if (genericIEnumerableType != null)
+                {
+                    elementType = genericIEnumerableType.GetGenericArguments()[0];
+                }
+                else
+                {
+                    elementType = typeof(object);
+                }
             }
 
-            return ClassType.Object;
-        }
+            if (typeof(IList).IsAssignableFrom(type))
+            {
+                addMethod = default;
 
-        public static bool IsDictionaryClassType(Type type)
-        {
-            return (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
-                type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
+                if (type.IsInterface)
+                {
+                    Type concreteListType = typeof(List<>).MakeGenericType(elementType);
+                    if (type.IsAssignableFrom(concreteListType))
+                    {
+                        runtimeType = concreteListType;
+                    }
+                }
+            }
+            else if (type.IsInterface)
+            {
+                addMethod = default;
+
+                Type concreteType = typeof(List<>).MakeGenericType(elementType);
+                if (type.IsAssignableFrom(concreteType))
+                {
+                    runtimeType = concreteType;
+                }
+                else
+                {
+                    concreteType = typeof(HashSet<>).MakeGenericType(elementType);
+                    if (type.IsAssignableFrom(concreteType))
+                    {
+                        runtimeType = concreteType;
+                    }
+                }
+            }
+            else
+            {
+                addMethod = default;
+
+                if (checkForAddMethod)
+                {
+                    Type genericICollectionType = type.GetInterface("System.Collections.Generic.ICollection`1");
+                    if (genericICollectionType != null)
+                    {
+                        addMethod = genericICollectionType.GetMethod("Add");
+                    }
+                    else
+                    {
+                        // Non-immutable stack or queue.
+                        MethodInfo methodInfo = type.GetMethod("Push") ?? type.GetMethod("Enqueue");
+                        if (methodInfo?.ReturnType == typeof(void))
+                        {
+                            addMethod = methodInfo;
+                        }
+                    }
+                }
+            }
+
+            return ClassType.Enumerable;
         }
     }
 }
index 9604b4a..b79a54b 100644 (file)
@@ -14,12 +14,8 @@ namespace System.Text.Json
     internal abstract class JsonPropertyInfo
     {
         // Cache the converters so they don't get created for every enumerable property.
-        private static readonly JsonEnumerableConverter s_jsonDerivedEnumerableConverter = new DefaultDerivedEnumerableConverter();
         private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
-        private static readonly JsonEnumerableConverter s_jsonICollectionConverter = new DefaultICollectionConverter();
         private static readonly JsonEnumerableConverter s_jsonImmutableEnumerableConverter = new DefaultImmutableEnumerableConverter();
-        private static readonly JsonDictionaryConverter s_jsonDerivedDictionaryConverter = new DefaultDerivedDictionaryConverter();
-        private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter();
         private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter();
 
         public static readonly JsonPropertyInfo s_missingProperty = GetMissingProperty();
@@ -29,6 +25,7 @@ namespace System.Text.Json
         private JsonClassInfo _declaredTypeClassInfo;
 
         public bool CanBeNull { get; private set; }
+        public bool IsImmutableArray { get; private set; }
 
         public ClassType ClassType;
 
@@ -54,13 +51,7 @@ namespace System.Text.Json
 
         public abstract IList CreateConverterList();
 
-        public abstract IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList);
-
-        public abstract object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary);
-
-        public abstract IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList);
-
-        public abstract IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary);
+        public abstract IDictionary CreateConverterDictionary();
 
         public abstract IEnumerable CreateImmutableCollectionInstance(ref ReadStack state, Type collectionType, string delegateKey, IList sourceList, JsonSerializerOptions options);
 
@@ -83,8 +74,6 @@ namespace System.Text.Json
 
         public Type DeclaredPropertyType { get; private set; }
 
-        public Type ImplementedPropertyType { get; private set; }
-
         private void DeterminePropertyName()
         {
             if (PropertyInfo == null)
@@ -132,9 +121,7 @@ namespace System.Text.Json
 
         private void DetermineSerializationCapabilities()
         {
-            if (ClassType != ClassType.Enumerable &&
-                ClassType != ClassType.Dictionary &&
-                ClassType != ClassType.IDictionaryConstructible)
+            if ((ClassType & (ClassType.Enumerable | ClassType.Dictionary)) == 0)
             {
                 // We serialize if there is a getter + not ignoring readonly properties.
                 ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties);
@@ -162,54 +149,17 @@ namespace System.Text.Json
 
                             EnumerableConverter = s_jsonArrayConverter;
                         }
-                        else if (ClassType == ClassType.IDictionaryConstructible)
+                        else if (ClassType == ClassType.Dictionary && DefaultImmutableDictionaryConverter.IsImmutableDictionary(RuntimePropertyType))
                         {
-                            // Natively supported type.
-                            if (DeclaredPropertyType == ImplementedPropertyType)
-                            {
-                                if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName))
-                                {
-                                    DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(
-                                        RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options);
-
-                                    DictionaryConverter = s_jsonImmutableDictionaryConverter;
-                                }
-                                else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType))
-                                {
-                                    DictionaryConverter = s_jsonIDictionaryConverter;
-                                }
-                            }
-                            // Type that implements a type with ClassType IDictionaryConstructible.
-                            else
-                            {
-                                DictionaryConverter = s_jsonDerivedDictionaryConverter;
-                            }
+                            DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, ElementType, Options);
+                            DictionaryConverter = s_jsonImmutableDictionaryConverter;
                         }
-                        else if (ClassType == ClassType.Enumerable)
+                        else if (ClassType == ClassType.Enumerable && DefaultImmutableEnumerableConverter.IsImmutableEnumerable(RuntimePropertyType, out bool isImmutableArray))
                         {
-                            // Else if it's an implementing type whose runtime type is not assignable to IList.
-                            if (DeclaredPropertyType != ImplementedPropertyType &&
-                                (!typeof(IList).IsAssignableFrom(RuntimePropertyType) ||
-                                ImplementedPropertyType == typeof(ArrayList) ||
-                                ImplementedPropertyType == typeof(IList)))
-                            {
-                                EnumerableConverter = s_jsonDerivedEnumerableConverter;
-                            }
-                            else if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) ||
-                                (!typeof(IList).IsAssignableFrom(RuntimePropertyType) &&
-                                JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType, Options)))
-                            {
-                                EnumerableConverter = s_jsonICollectionConverter;
-                            }
-                            else if (RuntimePropertyType.IsGenericType &&
-                                RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) &&
-                                RuntimePropertyType.GetGenericArguments().Length == 1)
-                            {
-                                DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType,
-                                    JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options);
-                                EnumerableConverter = s_jsonImmutableEnumerableConverter;
-                            }
+                            DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, ElementType, Options);
+                            EnumerableConverter = s_jsonImmutableEnumerableConverter;
 
+                            IsImmutableArray = isImmutableArray;
                         }
                     }
                 }
@@ -230,8 +180,7 @@ namespace System.Text.Json
                 if (_elementClassInfo == null && ElementType != null)
                 {
                     Debug.Assert(ClassType == ClassType.Enumerable ||
-                        ClassType == ClassType.Dictionary ||
-                        ClassType == ClassType.IDictionaryConstructible);
+                        ClassType == ClassType.Dictionary);
 
                     _elementClassInfo = Options.GetOrAddClass(ElementType);
                 }
@@ -256,8 +205,6 @@ namespace System.Text.Json
 
         public abstract Type GetDictionaryConcreteType();
 
-        public abstract Type GetConcreteType(Type type);
-
         public virtual void GetPolicies()
         {
             DetermineSerializationCapabilities();
@@ -276,52 +223,47 @@ namespace System.Text.Json
             Type parentClassType,
             Type declaredPropertyType,
             Type runtimePropertyType,
-            Type implementedPropertyType,
+            ClassType runtimeClassType,
             PropertyInfo propertyInfo,
             Type elementType,
             JsonConverter converter,
+            bool treatAsNullable,
             JsonSerializerOptions options)
         {
             ParentClassType = parentClassType;
             DeclaredPropertyType = declaredPropertyType;
             RuntimePropertyType = runtimePropertyType;
-            ImplementedPropertyType = implementedPropertyType;
+            ClassType = runtimeClassType;
             PropertyInfo = propertyInfo;
             ElementType = elementType;
             Options = options;
-            IsNullableType = runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
-            CanBeNull = IsNullableType || !runtimePropertyType.IsValueType;
+            CanBeNull = treatAsNullable || !runtimePropertyType.IsValueType;
 
             if (converter != null)
             {
                 ConverterBase = converter;
 
                 HasInternalConverter = (converter.GetType().Assembly == GetType().Assembly);
-
-                // Avoid calling GetClassType since it will re-ask if there is a converter which is slow.
-                if (runtimePropertyType == typeof(object))
-                {
-                    ClassType = ClassType.Unknown;
-                }
-                else
-                {
-                    ClassType = ClassType.Value;
-                }
-            }
-            // Special case for immutable collections.
-            else if (declaredPropertyType != implementedPropertyType && !JsonClassInfo.IsNativelySupportedCollection(declaredPropertyType))
-            {
-                ClassType = JsonClassInfo.GetClassType(declaredPropertyType, options);
-            }
-            else
-            {
-                ClassType = JsonClassInfo.GetClassType(runtimePropertyType, options);
             }
         }
 
-        public bool IgnoreNullValues { get; private set; }
+        public abstract bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate);
 
-        public bool IsNullableType { get; private set; }
+        public abstract object CreateEnumerableAddMethod(MethodInfo addMethod, object target);
+
+        public abstract void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value);
+
+        public abstract void AddObjectToParentEnumerable(object addMethodDelegate, object value);
+
+        public abstract void AddObjectToDictionary(object target, string key, object value);
+
+        public abstract void AddObjectToParentDictionary(object target, string key, object value);
+
+        public abstract bool CanPopulateDictionary(object target);
+
+        public abstract bool ParentDictionaryCanBePopulated(object target);
+
+        public bool IgnoreNullValues { get; private set; }
 
         public bool IsPropertyPolicy { get; protected set; }
 
index 3eb08b7..5a2dae1 100644 (file)
@@ -18,25 +18,33 @@ namespace System.Text.Json
         public Func<object, TDeclaredProperty> Get { get; private set; }
         public Action<object, TDeclaredProperty> Set { get; private set; }
 
+        public Action<TDeclaredProperty> AddItemToEnumerable { get; private set; }
+
         public JsonConverter<TConverter> Converter { get; internal set; }
 
         public override void Initialize(
             Type parentClassType,
             Type declaredPropertyType,
             Type runtimePropertyType,
-            Type implementedPropertyType,
+            ClassType runtimeClassType,
             PropertyInfo propertyInfo,
             Type elementType,
             JsonConverter converter,
+            bool treatAsNullable,
             JsonSerializerOptions options)
         {
-            base.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, elementType, converter, options);
-
-            if (propertyInfo != null &&
-                // We only want to get the getter and setter if we are going to use them.
-                // If the declared type is not the property info type, then we are just
-                // getting metadata on how best to (de)serialize derived types.
-                declaredPropertyType == propertyInfo.PropertyType)
+            base.Initialize(
+                parentClassType,
+                declaredPropertyType,
+                runtimePropertyType,
+                runtimeClassType,
+                propertyInfo,
+                elementType,
+                converter,
+                treatAsNullable,
+                options);
+
+            if (propertyInfo != null)
             {
                 if (propertyInfo.GetMethod?.IsPublic == true)
                 {
@@ -97,187 +105,102 @@ namespace System.Text.Json
             }
         }
 
-        public override IList CreateConverterList()
-        {
-            return new List<TDeclaredProperty>();
-        }
+        private JsonPropertyInfo _elementPropertyInfo;
 
-        public override Type GetConcreteType(Type parentType)
+        private void SetPropertyInfoForObjectElement()
         {
-            if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType))
-            {
-                return typeof(List<TDeclaredProperty>);
-            }
-            else if (JsonClassInfo.IsSetInterface(parentType))
+            if (_elementPropertyInfo == null && ElementClassInfo.PolicyProperty == null)
             {
-                return typeof(HashSet<TDeclaredProperty>);
+                _elementPropertyInfo = ElementClassInfo.CreateRootObject(Options);
             }
-
-            return parentType;
         }
 
-        public override IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList)
+        public override bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate)
         {
-            // Implementing types that don't have default constructors are not supported for deserialization.
-            if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
-            {
-                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
-                    collectionPropertyInfo.DeclaredPropertyType,
-                    collectionPropertyInfo.ParentClassType,
-                    collectionPropertyInfo.PropertyInfo);
-            }
+            SetPropertyInfoForObjectElement();
+            Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
 
-            object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
+            addMethodDelegate = (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).CreateEnumerableAddMethod(RuntimeClassInfo.AddItemToObject, target);
+            return addMethodDelegate != null;
+        }
 
-            if (instance is IList instanceOfIList)
-            {
-                if (!instanceOfIList.IsReadOnly)
-                {
-                    foreach (object item in sourceList)
-                    {
-                        instanceOfIList.Add(item);
-                    }
-                    return instanceOfIList;
-                }
-            }
-            else if (instance is ICollection<TDeclaredProperty> instanceOfICollection)
-            {
-                if (!instanceOfICollection.IsReadOnly)
-                {
-                    foreach (TDeclaredProperty item in sourceList)
-                    {
-                        instanceOfICollection.Add(item);
-                    }
-                    return instanceOfICollection;
-                }
-            }
-            else if (instance is Stack<TDeclaredProperty> instanceOfStack)
-            {
-                foreach (TDeclaredProperty item in sourceList)
-                {
-                    instanceOfStack.Push(item);
-                }
-                return instanceOfStack;
-            }
-            else if (instance is Queue<TDeclaredProperty> instanceOfQueue)
+        public override object CreateEnumerableAddMethod(MethodInfo addMethod, object target)
+        {
+            if (target is ICollection<TDeclaredProperty> collection && collection.IsReadOnly)
             {
-                foreach (TDeclaredProperty item in sourceList)
-                {
-                    instanceOfQueue.Enqueue(item);
-                }
-                return instanceOfQueue;
+                return null;
             }
 
-            // TODO (https://github.com/dotnet/corefx/issues/40479):
-            // Use reflection to support types implementing Stack or Queue.
+            return Options.MemberAccessorStrategy.CreateAddDelegate<TDeclaredProperty>(addMethod, target);
+        }
 
-            throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
-                collectionPropertyInfo.DeclaredPropertyType,
-                collectionPropertyInfo.ParentClassType,
-                collectionPropertyInfo.PropertyInfo);
+        public override void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value)
+        {
+            Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+            (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentEnumerable(addMethodDelegate, value);
         }
 
-        public override object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary)
+        public override void AddObjectToParentEnumerable(object addMethodDelegate, object value)
         {
-            // Implementing types that don't have default constructors are not supported for deserialization.
-            if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
-            {
-                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
-                    collectionPropertyInfo.DeclaredPropertyType,
-                    collectionPropertyInfo.ParentClassType,
-                    collectionPropertyInfo.PropertyInfo);
-            }
+            ((Action<TDeclaredProperty>)addMethodDelegate)((TDeclaredProperty)value);
+        }
 
-            object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
+        public override void AddObjectToDictionary(object target, string key, object value)
+        {
+            Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+            (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentDictionary(target, key, value);
+        }
 
-            if (instance is IDictionary instanceOfIDictionary)
+        public override void AddObjectToParentDictionary(object target, string key, object value)
+        {
+            if (target is IDictionary<string, TDeclaredProperty> genericDict)
             {
-                if (!instanceOfIDictionary.IsReadOnly)
-                {
-                    foreach (DictionaryEntry entry in sourceDictionary)
-                    {
-                        instanceOfIDictionary.Add((string)entry.Key, entry.Value);
-                    }
-                    return instanceOfIDictionary;
-                }
+                Debug.Assert(!genericDict.IsReadOnly);
+                genericDict[key] = (TDeclaredProperty)value;
             }
-            else if (instance is IDictionary<string, TDeclaredProperty> instanceOfGenericIDictionary)
+            else
             {
-                if (!instanceOfGenericIDictionary.IsReadOnly)
-                {
-                    foreach (DictionaryEntry entry in sourceDictionary)
-                    {
-                        instanceOfGenericIDictionary.Add((string)entry.Key, (TDeclaredProperty)entry.Value);
-                    }
-                    return instanceOfGenericIDictionary;
-                }
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(target.GetType(), parentType: null, memberInfo: null);
             }
+        }
 
-            // TODO (https://github.com/dotnet/corefx/issues/40479):
-            // Use reflection to support types implementing SortedList and maybe immutable dictionaries.
-
-            // Types implementing SortedList and immutable dictionaries will fail here.
-            throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
-                collectionPropertyInfo.DeclaredPropertyType,
-                collectionPropertyInfo.ParentClassType,
-                collectionPropertyInfo.PropertyInfo);
+        public override bool CanPopulateDictionary(object target)
+        {
+            SetPropertyInfoForObjectElement();
+            Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+            return (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).ParentDictionaryCanBePopulated(target);
         }
 
-        public override IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList)
+        public override bool ParentDictionaryCanBePopulated(object target)
         {
-            if (parentType.IsGenericType)
+            if (target is IDictionary<string, TDeclaredProperty> genericDict && !genericDict.IsReadOnly)
             {
-                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);
+                return true;
             }
-            else
+            else if (target is IDictionary dict && !dict.IsReadOnly)
             {
-                if (parentType == typeof(ArrayList))
-                {
-                    return new ArrayList(sourceList);
-                }
-                // Stack and Queue go into this condition, until we support with reflection.
-                else
+                Type genericDictType = target.GetType().GetInterface("System.Collections.Generic.IDictionary`2") ??
+                    target.GetType().GetInterface("System.Collections.Generic.IReadOnlyDictionary`2");
+
+                if (genericDictType != null && genericDictType.GetGenericArguments()[0] != typeof(string))
                 {
-                    return (IEnumerable)Activator.CreateInstance(parentType, sourceList);
+                    return false;
                 }
+
+                return true;
             }
+
+            return false;
         }
 
-        public override IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary)
+        public override IList CreateConverterList()
         {
-            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);
-            }
+            return new List<TDeclaredProperty>();
+        }
+
+        public override IDictionary CreateConverterDictionary()
+        {
+            return new Dictionary<string, TDeclaredProperty>();
         }
 
         // Creates an IEnumerable<TDeclaredPropertyType> and populates it with the items in the
@@ -311,13 +234,5 @@ namespace System.Text.Json
 
             return collection;
         }
-
-        private IEnumerable<TDeclaredProperty> CreateGenericTDeclaredPropertyIEnumerable(IList sourceList)
-        {
-            foreach (object item in sourceList)
-            {
-                yield return (TDeclaredProperty)item;
-            }
-        }
     }
 }
index eb90e96..6dcc33f 100644 (file)
@@ -40,7 +40,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
-            if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+            if (state.Current.KeyName == null && state.Current.IsProcessingDictionary())
             {
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
@@ -101,6 +101,7 @@ namespace System.Text.Json
                 Debug.Assert(current.CollectionEnumerator != null);
 
                 TConverter value;
+
                 if (current.CollectionEnumerator is IEnumerator<TConverter> enumerator)
                 {
                     // Avoid boxing for strongly-typed enumerators such as returned from IList<T>.
index 4216239..3b2f63c 100644 (file)
@@ -42,7 +42,7 @@ namespace System.Text.Json.Serialization
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
-            if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+            if (state.Current.KeyName == null && state.Current.IsProcessingDictionary())
             {
                 ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
index 724578e..f0abea1 100644 (file)
@@ -91,10 +91,20 @@ namespace System.Text.Json
                 key = enumerator.Current.Key;
                 value = enumerator.Current.Value;
             }
-            else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
+            else
             {
-                key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
-                value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+                if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString)
+                {
+                    key = keyAsString;
+                    value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+                }
+                else
+                {
+                    throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                        current.JsonPropertyInfo.DeclaredPropertyType,
+                        current.JsonPropertyInfo.ParentClassType,
+                        current.JsonPropertyInfo.PropertyInfo);
+                }
             }
 
             Debug.Assert(key != null);
index 2d56b20..073a73f 100644 (file)
@@ -5,16 +5,14 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        private static void HandleStartArray(
-            JsonSerializerOptions options,
-            ref Utf8JsonReader reader,
-            ref ReadStack state)
+        private static void HandleStartArray(JsonSerializerOptions options, ref ReadStack state)
         {
             if (state.Current.SkipProperty)
             {
@@ -58,19 +56,20 @@ namespace System.Text.Json
             Debug.Assert(state.Current.JsonClassInfo.ClassType != ClassType.Value);
 
             // Set or replace the existing enumerable value.
-            object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state);
+            object value = ReadStackFrame.CreateEnumerableValue(ref state);
 
             // If value is not null, then we don't have a converter so apply the value.
             if (value != null)
             {
+                state.Current.DetermineEnumerablePopulationStrategy(value);
+
                 if (state.Current.ReturnValue != null)
                 {
                     state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
                 }
                 else
                 {
-                    // Primitive arrays being returned without object
-                    state.Current.SetReturnValue(value);
+                    state.Current.ReturnValue = value;
                 }
             }
         }
@@ -133,7 +132,6 @@ namespace System.Text.Json
             }
 
             ApplyObjectToEnumerable(value, ref state);
-
             return false;
         }
 
@@ -153,56 +151,80 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    if (!(state.Current.ReturnValue is IList list))
+                    if (state.Current.AddObjectToEnumerable == null)
                     {
-                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
-                        return;
+                        if (state.Current.ReturnValue is IList list)
+                        {
+                            list.Add(value);
+                        }
+                        else
+                        {
+                            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
+                            return;
+                        }
+                    }
+                    else
+                    {
+                        state.Current.JsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value);
                     }
-                    list.Add(value);
                 }
             }
             else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable))
             {
                 Debug.Assert(state.Current.JsonPropertyInfo != null);
                 Debug.Assert(state.Current.ReturnValue != null);
+
                 if (state.Current.TempEnumerableValues != null)
                 {
                     state.Current.TempEnumerableValues.Add(value);
                 }
                 else
                 {
-                    IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
-                    if (list == null ||
+                    JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+                    object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+                    if (currentEnumerable == null ||
                         // ImmutableArray<T> is a struct, so default value won't be null.
-                        state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName))
+                        jsonPropertyInfo.IsImmutableArray)
+                    {
+                        jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+                    }
+                    else if (state.Current.AddObjectToEnumerable == null)
                     {
-                        state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+                        ((IList)currentEnumerable).Add(value);
                     }
                     else
                     {
-                        list.Add(value);
+                        jsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value);
                     }
                 }
-            }
-            else if (state.Current.IsProcessingObject(ClassType.Dictionary) ||
-                (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
-            {
-                Debug.Assert(state.Current.ReturnValue != null);
-                IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
 
-                string key = state.Current.KeyName;
-                Debug.Assert(!string.IsNullOrEmpty(key));
-                dictionary[key] = value;
             }
-            else if (state.Current.IsProcessingObject(ClassType.IDictionaryConstructible) ||
-                (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly))
+            else if (state.Current.IsProcessingObject(ClassType.Dictionary) || (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
             {
-                Debug.Assert(state.Current.TempDictionaryValues != null);
-                IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues;
-
                 string key = state.Current.KeyName;
                 Debug.Assert(!string.IsNullOrEmpty(key));
-                dictionary[key] = value;
+
+                if (state.Current.TempDictionaryValues != null)
+                {
+                    (state.Current.TempDictionaryValues)[key] = value;
+                }
+                else
+                {
+                    Debug.Assert(state.Current.ReturnValue != null);
+
+                    object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+
+                    if (currentDictionary is IDictionary dict)
+                    {
+                        Debug.Assert(!dict.IsReadOnly);
+                        dict[key] = value;
+                    }
+                    else
+                    {
+                        state.Current.JsonPropertyInfo.AddObjectToDictionary(currentDictionary, key, value);
+                    }
+                }
             }
             else
             {
@@ -226,47 +248,65 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    ((IList<TProperty>)state.Current.ReturnValue).Add(value);
+                    AddValueToEnumerable(ref state, state.Current.ReturnValue, value);
                 }
             }
             else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
             {
-                Debug.Assert(state.Current.JsonPropertyInfo != null);
-                Debug.Assert(state.Current.ReturnValue != null);
                 if (state.Current.TempEnumerableValues != null)
                 {
                     ((IList<TProperty>)state.Current.TempEnumerableValues).Add(value);
                 }
                 else
                 {
-                    IList<TProperty> list = (IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
-                    if (list == null)
+                    Debug.Assert(state.Current.JsonPropertyInfo != null);
+                    Debug.Assert(state.Current.ReturnValue != null);
+
+                    JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+                    object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+                    if (currentEnumerable == null ||
+                        // ImmutableArray<T> is a struct, so default value won't be null.
+                        jsonPropertyInfo.IsImmutableArray)
                     {
-                        state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+                        jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
                     }
                     else
                     {
-                        list.Add(value);
+                        AddValueToEnumerable(ref state, currentEnumerable, value);
                     }
                 }
             }
             else if (state.Current.IsProcessingDictionary())
             {
-                Debug.Assert(state.Current.ReturnValue != null);
-                IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
-
                 string key = state.Current.KeyName;
                 Debug.Assert(!string.IsNullOrEmpty(key));
-                dictionary[key] = value;
-            }
-            else if (state.Current.IsProcessingIDictionaryConstructible())
-            {
-                Debug.Assert(state.Current.TempDictionaryValues != null);
-                IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.TempDictionaryValues;
 
-                string key = state.Current.KeyName;
-                Debug.Assert(!string.IsNullOrEmpty(key));
-                dictionary[key] = value;
+                if (state.Current.TempDictionaryValues != null)
+                {
+                    ((IDictionary<string, TProperty>)state.Current.TempDictionaryValues)[key] = value;
+                }
+                else
+                {
+                    Debug.Assert(state.Current.ReturnValue != null);
+
+                    object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+
+                    if (currentDictionary is IDictionary<string, TProperty> genericDict)
+                    {
+                        Debug.Assert(!genericDict.IsReadOnly);
+                        genericDict[key] = value;
+                    }
+                    else if (currentDictionary is IDictionary dict)
+                    {
+                        Debug.Assert(!dict.IsReadOnly);
+                        dict[key] = value;
+                    }
+                    else
+                    {
+                        throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(currentDictionary.GetType(), parentType: null, memberInfo: null);
+                    }
+                }
             }
             else
             {
@@ -274,5 +314,25 @@ namespace System.Text.Json
                 state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
             }
         }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void AddValueToEnumerable<TProperty>(ref ReadStack state, object target, TProperty value)
+        {
+            if (target is IList<TProperty> genericList)
+            {
+                Debug.Assert(!genericList.IsReadOnly);
+                genericList.Add(value);
+            }
+            else if (target is IList list)
+            {
+                Debug.Assert(!list.IsReadOnly);
+                list.Add(value);
+            }
+            else
+            {
+                Debug.Assert(state.Current.AddObjectToEnumerable != null);
+                ((Action<TProperty>)state.Current.AddObjectToEnumerable)(value);
+            }
+        }
     }
 }
index 8eef116..3029f02 100644 (file)
@@ -42,11 +42,7 @@ namespace System.Text.Json
 
                 JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-                if (state.Current.IsProcessingIDictionaryConstructible())
-                {
-                    state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
-                }
-                else
+                if (state.Current.IsProcessingEnumerable())
                 {
                     if (classInfo.CreateObject == null)
                     {
@@ -55,43 +51,40 @@ namespace System.Text.Json
                     }
                     state.Current.ReturnValue = classInfo.CreateObject();
                 }
+                else if (state.Current.IsProcessingDictionary())
+                {
+                    object dictValue = ReadStackFrame.CreateDictionaryValue(ref state);
+
+                    // If value is not null, then we don't have a converter so apply the value.
+                    if (dictValue != null)
+                    {
+                        state.Current.ReturnValue = dictValue;
+                        state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue);
+                    }
+                }
+                else
+                {
+                    state.Current.ReturnValue = classInfo.CreateObject();
+                }
 
                 return;
             }
 
             state.Current.CollectionPropertyInitialized = true;
 
-            if (state.Current.IsProcessingIDictionaryConstructible())
+            object value = ReadStackFrame.CreateDictionaryValue(ref state);
+            if (value != null)
             {
-                JsonClassInfo dictionaryClassInfo;
-                if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType)
+                state.Current.DetermineIfDictionaryCanBePopulated(value);
+
+                if (state.Current.ReturnValue != null)
                 {
-                    dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
+                    state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
                 }
                 else
                 {
-                    dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType);
-                }
-
-                state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateConcreteDictionary();
-            }
-            else
-            {
-                // Create the dictionary.
-                JsonClassInfo dictionaryClassInfo = jsonPropertyInfo.RuntimeClassInfo;
-                IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject();
-
-                if (value != null)
-                {
-                    if (state.Current.ReturnValue != null)
-                    {
-                        state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
-                    }
-                    else
-                    {
-                        // A dictionary is being returned directly, or a nested dictionary.
-                        state.Current.SetReturnValue(value);
-                    }
+                    // A dictionary is being returned directly, or a nested dictionary.
+                    state.Current.ReturnValue = value;
                 }
             }
         }
@@ -102,26 +95,28 @@ namespace System.Text.Json
 
             if (state.Current.IsProcessingProperty(ClassType.Dictionary))
             {
-                // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property.
-                // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject
-                // encountered here is from the outer object so forward to HandleEndObject().
-                if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo)
+                if (state.Current.TempDictionaryValues != null)
                 {
-                    HandleEndObject(ref state);
+                    JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
+                    state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options));
+                    state.Current.EndProperty();
                 }
                 else
                 {
-                    // We added the items to the dictionary already.
-                    state.Current.EndProperty();
+                    // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property.
+                    // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject
+                    // encountered here is from the outer object so forward to HandleEndObject().
+                    if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo)
+                    {
+                        HandleEndObject(ref state);
+                    }
+                    else
+                    {
+                        // We added the items to the dictionary already.
+                        state.Current.EndProperty();
+                    }
                 }
             }
-            else if (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible))
-            {
-                Debug.Assert(state.Current.TempDictionaryValues != null);
-                JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
-                state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options));
-                state.Current.EndProperty();
-            }
             else
             {
                 object value;
index fd6c9d8..c3878ed 100644 (file)
@@ -8,7 +8,7 @@ namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state)
+        private static bool HandleNull(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
         {
             if (state.Current.SkipProperty)
             {
@@ -82,7 +82,6 @@ namespace System.Text.Json
             JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.ElementClassInfo.PolicyProperty;
 
             // if elementPropertyInfo == null then this element doesn't need a converter (an object).
-
             if (elementPropertyInfo?.CanBeNull == false)
             {
                 // Allow a value type converter to return a null value representation.
index c0b551c..e80f7b8 100644 (file)
@@ -11,7 +11,7 @@ namespace System.Text.Json
     {
         private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state)
         {
-            Debug.Assert(!state.Current.IsProcessingDictionaryOrIDictionaryConstructible());
+            Debug.Assert(!state.Current.IsProcessingDictionary());
 
             if (state.Current.IsProcessingEnumerable())
             {
@@ -42,9 +42,16 @@ namespace System.Text.Json
                 }
             }
 
-            if (state.Current.IsProcessingIDictionaryConstructible())
+            if (state.Current.IsProcessingObject(ClassType.Dictionary))
             {
-                state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
+                object value = ReadStackFrame.CreateDictionaryValue(ref state);
+
+                // If value is not null, then we don't have a converter so apply the value.
+                if (value != null)
+                {
+                    state.Current.ReturnValue = value;
+                    state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue);
+                }
             }
             else
             {
@@ -55,9 +62,7 @@ namespace System.Text.Json
         private static void HandleEndObject(ref ReadStack state)
         {
             // Only allow dictionaries to be processed here if this is the DataExtensionProperty.
-            Debug.Assert(
-                (!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) &&
-                !state.Current.IsProcessingIDictionaryConstructible());
+            Debug.Assert(!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo);
 
             // Check if we are trying to build the sorted cache.
             if (state.Current.PropertyRefCache != null)
@@ -75,6 +80,7 @@ namespace System.Text.Json
             else
             {
                 state.Pop();
+
                 ApplyObjectToEnumerable(value, ref state);
             }
         }
index 1aec551..890e57e 100644 (file)
@@ -23,11 +23,11 @@ namespace System.Text.Json
                 return;
             }
 
-            Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default);
-            Debug.Assert(state.Current.JsonClassInfo != default);
+            Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null);
+            Debug.Assert(state.Current.JsonClassInfo != null);
 
-            bool isProcessingDictObject = state.Current.IsProcessingDictionaryOrIDictionaryConstructibleObject();
-            if ((isProcessingDictObject || state.Current.IsProcessingDictionaryOrIDictionaryConstructibleProperty()) &&
+            bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary);
+            if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) &&
                 state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo)
             {
                 if (isProcessingDictObject)
index 5ff0073..d3f8ce8 100644 (file)
@@ -68,7 +68,7 @@ namespace System.Text.Json
                                 break;
                             }
                         }
-                        else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+                        else if (readStack.Current.IsProcessingDictionary())
                         {
                             HandleStartDictionary(options, ref readStack);
                         }
@@ -87,7 +87,7 @@ namespace System.Text.Json
                             // A non-dictionary property can also have EndProperty() called when completed, although it is redundant.
                             readStack.Current.EndProperty();
                         }
-                        else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+                        else if (readStack.Current.IsProcessingDictionary())
                         {
                             HandleEndDictionary(options, ref readStack);
                         }
@@ -100,7 +100,7 @@ namespace System.Text.Json
                     {
                         if (!readStack.Current.IsProcessingValue())
                         {
-                            HandleStartArray(options, ref reader, ref readStack);
+                            HandleStartArray(options, ref readStack);
                         }
                         else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed))
                         {
@@ -114,7 +114,7 @@ namespace System.Text.Json
                     }
                     else if (tokenType == JsonTokenType.Null)
                     {
-                        HandleNull(ref reader, ref readStack);
+                        HandleNull(options, ref reader, ref readStack);
                     }
                 }
             }
index 067e129..f0cf28c 100644 (file)
@@ -127,19 +127,20 @@ namespace System.Text.Json
                 key = polymorphicEnumerator.Current.Key;
                 value = (TProperty)polymorphicEnumerator.Current.Value;
             }
-            else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
-            {
-                key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
-                value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
-            }
             else
             {
-                // Todo: support non-generic Dictionary here (IDictionaryEnumerator)
-                // https://github.com/dotnet/corefx/issues/41034
-                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
-                    current.JsonPropertyInfo.DeclaredPropertyType,
-                    current.JsonPropertyInfo.ParentClassType,
-                    current.JsonPropertyInfo.PropertyInfo);
+                if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString)
+                {
+                    key = keyAsString;
+                    value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+                }
+                else
+                {
+                    throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                        current.JsonPropertyInfo.DeclaredPropertyType,
+                        current.JsonPropertyInfo.ParentClassType,
+                        current.JsonPropertyInfo.PropertyInfo);
+                }
             }
 
             if (value == null)
index 0b35eb2..540c86e 100644 (file)
@@ -125,21 +125,6 @@ namespace System.Text.Json
                 return;
             }
 
-            // A property that returns a type that is deserialized by passing an
-            // IDictionary to its constructor keeps the same stack frame.
-            if (jsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible)
-            {
-                state.Current.IsIDictionaryConstructibleProperty = true;
-
-                bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state);
-                if (endOfEnumerable)
-                {
-                    state.Current.MoveToNextProperty = true;
-                }
-
-                return;
-            }
-
             // A property that returns an object.
             if (!obtainedValue)
             {
index b0effbf..5f25813 100644 (file)
@@ -36,7 +36,6 @@ namespace System.Text.Json
                             finishedSerializing = true;
                             break;
                         case ClassType.Dictionary:
-                        case ClassType.IDictionaryConstructible:
                             finishedSerializing = HandleDictionary(state.Current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
                             break;
                         default:
index ff71f9d..c4f7850 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Collections.Generic;
 using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Text.Json.Serialization;
@@ -20,7 +19,6 @@ namespace System.Text.Json
         internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions();
 
         private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
-        private readonly ConcurrentDictionary<Type, JsonPropertyInfo> _objectJsonProperties = new ConcurrentDictionary<Type, JsonPropertyInfo>();
         private static readonly ConcurrentDictionary<string, ImmutableCollectionCreator> s_createRangeDelegates = new ConcurrentDictionary<string, ImmutableCollectionCreator>();
         private MemberAccessor _memberAccessorStrategy;
         private JsonNamingPolicy _dictionayKeyPolicy;
@@ -351,24 +349,6 @@ namespace System.Text.Json
             };
         }
 
-        internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(Type objectType, JsonSerializerOptions options)
-        {
-            if (!_objectJsonProperties.TryGetValue(objectType, out JsonPropertyInfo propertyInfo))
-            {
-                propertyInfo = JsonClassInfo.CreateProperty(
-                    objectType,
-                    objectType,
-                    objectType,
-                    propertyInfo: null,
-                    typeof(object),
-                    converter: null,
-                    options);
-                _objectJsonProperties[objectType] = propertyInfo;
-            }
-
-            return propertyInfo;
-        }
-
         internal bool CreateRangeDelegatesContainsKey(string key)
         {
             return s_createRangeDelegates.ContainsKey(key);
@@ -384,7 +364,6 @@ namespace System.Text.Json
             return s_createRangeDelegates.TryAdd(key, createRangeDelegate);
         }
 
-
         internal void VerifyMutable()
         {
             // The default options are hidden and thus should be immutable.
index bb513ac..c3b31ad 100644 (file)
@@ -11,6 +11,8 @@ namespace System.Text.Json
     {
         public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType);
 
+        public abstract Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target);
+
         public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType);
 
         public abstract ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType);
index 41c1d0b..f63a6c6 100644 (file)
@@ -82,13 +82,12 @@ namespace System.Text.Json
                 }
                 else if (frame.IsProcessingEnumerable())
                 {
-                    // For enumerables add the index.
                     IList list = frame.TempEnumerableValues;
                     if (list == null && frame.ReturnValue != null)
                     {
+
                         list = (IList)frame.JsonPropertyInfo?.GetValueAsObject(frame.ReturnValue);
                     }
-
                     if (list != null)
                     {
                         sb.Append(@"[");
index 70a0438..bc17f4e 100644 (file)
@@ -26,6 +26,9 @@ namespace System.Text.Json
         // Current property values.
         public JsonPropertyInfo JsonPropertyInfo;
 
+        // Delegate used to add elements to the current property.
+        public object AddObjectToEnumerable;
+
         // Support System.Array and other types that don't implement IList.
         public IList TempEnumerableValues;
 
@@ -45,28 +48,28 @@ namespace System.Text.Json
         public List<PropertyRef> PropertyRefCache;
 
         /// <summary>
-        /// Is the current object an Enumerable, Dictionary or IDictionaryConstructible.
+        /// Is the current object an Enumerable or Dictionary.
         /// </summary>
         public bool IsProcessingCollectionObject()
         {
-            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary);
         }
 
         /// <summary>
-        /// Is the current property an Enumerable, Dictionary or IDictionaryConstructible.
+        /// Is the current property an Enumerable or Dictionary.
         /// </summary>
         public bool IsProcessingCollectionProperty()
         {
-            return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+            return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary);
         }
 
         /// <summary>
-        /// Is the current object or property an Enumerable, Dictionary or IDictionaryConstructible.
+        /// Is the current object or property an Enumerable or Dictionary.
         /// </summary>
         public bool IsProcessingCollection()
         {
-            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
-                IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary) ||
+                IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary);
         }
 
         /// <summary>
@@ -79,40 +82,6 @@ namespace System.Text.Json
         }
 
         /// <summary>
-        /// Is the current object or property an IDictionaryConstructible.
-        /// </summary>
-        public bool IsProcessingIDictionaryConstructible()
-        {
-            return IsProcessingObject(ClassType.IDictionaryConstructible)
-                || IsProcessingProperty(ClassType.IDictionaryConstructible);
-        }
-
-        /// <summary>
-        /// Is the current object a Dictionary or IDictionaryConstructible.
-        /// </summary>
-        public bool IsProcessingDictionaryOrIDictionaryConstructibleObject()
-        {
-            return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible);
-        }
-
-        /// <summary>
-        /// Is the current property a Dictionary or IDictionaryConstructible.
-        /// </summary>
-        public bool IsProcessingDictionaryOrIDictionaryConstructibleProperty()
-        {
-            return IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible);
-        }
-
-        /// <summary>
-        /// Is the current object or property a Dictionary or IDictionaryConstructible.
-        /// </summary>
-        public bool IsProcessingDictionaryOrIDictionaryConstructible()
-        {
-            return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
-                IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible);
-        }
-
-        /// <summary>
         /// Is the current object or property an Enumerable.
         /// </summary>
         public bool IsProcessingEnumerable()
@@ -182,7 +151,7 @@ namespace System.Text.Json
 
         public void InitializeJsonPropertyInfo()
         {
-            if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible))
+            if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary))
             {
                 JsonPropertyInfo = JsonClassInfo.PolicyProperty;
             }
@@ -205,6 +174,7 @@ namespace System.Text.Json
 
         public void EndProperty()
         {
+            AddObjectToEnumerable = null;
             CollectionPropertyInitialized = false;
             JsonPropertyInfo = null;
             TempEnumerableValues = null;
@@ -213,7 +183,7 @@ namespace System.Text.Json
             KeyName = null;
         }
 
-        public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state)
+        public static object CreateEnumerableValue(ref ReadStack state)
         {
             JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
 
@@ -235,7 +205,7 @@ namespace System.Text.Json
 
                 // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection.
                 if (!jsonPropertyInfo.IsPropertyPolicy &&
-                    !state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName))
+                    !state.Current.JsonPropertyInfo.IsImmutableArray)
                 {
                     jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null);
                 }
@@ -243,35 +213,62 @@ namespace System.Text.Json
                 return null;
             }
 
-            Type propertyType = jsonPropertyInfo.RuntimePropertyType;
-            if (typeof(IList).IsAssignableFrom(propertyType))
+            JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+            if (runtimeClassInfo.CreateObject != null)
+            {
+                return runtimeClassInfo.CreateObject();
+            }
+            else
             {
-                // If IList, add the members as we create them.
-                JsonClassInfo collectionClassInfo;
+                // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor.
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    jsonPropertyInfo.DeclaredPropertyType,
+                    jsonPropertyInfo.ParentClassType,
+                    jsonPropertyInfo.PropertyInfo);
+            }
+        }
 
-                if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType)
+        public static object CreateDictionaryValue(ref ReadStack state)
+        {
+            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+            // If the property has a DictionaryConverter, then we use tempDictionaryValues.
+            if (jsonPropertyInfo.DictionaryConverter != null)
+            {
+                IDictionary converterDictionary;
+                JsonClassInfo elementClassInfo = jsonPropertyInfo.ElementClassInfo;
+                if (elementClassInfo.ClassType == ClassType.Value)
                 {
-                    collectionClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+                    converterDictionary = elementClassInfo.PolicyProperty.CreateConverterDictionary();
                 }
                 else
                 {
-                    collectionClassInfo = jsonPropertyInfo.DeclaredTypeClassInfo;
+                    converterDictionary = new Dictionary<string, object>();
                 }
 
-                if (collectionClassInfo.CreateObject() is IList collection)
-                {
-                    return collection;
-                }
-                else
+                state.Current.TempDictionaryValues = converterDictionary;
+
+                // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection.
+                if (!jsonPropertyInfo.IsPropertyPolicy)
                 {
-                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.DeclaredPropertyType);
-                    return null;
+                    jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null);
                 }
+
+                return null;
+            }
+
+            JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+            if (runtimeClassInfo.CreateObject != null)
+            {
+                return runtimeClassInfo.CreateObject();
             }
             else
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType);
-                return null;
+                // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor.
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    jsonPropertyInfo.DeclaredPropertyType,
+                    jsonPropertyInfo.ParentClassType,
+                    jsonPropertyInfo.PropertyInfo);
             }
         }
 
@@ -304,10 +301,50 @@ namespace System.Text.Json
             return current.TempEnumerableValues;
         }
 
-        public void SetReturnValue(object value)
+        public void DetermineEnumerablePopulationStrategy(object targetEnumerable)
+        {
+            if (JsonPropertyInfo.RuntimeClassInfo.AddItemToObject != null)
+            {
+                if (!JsonPropertyInfo.TryCreateEnumerableAddMethod(targetEnumerable, out object addMethodDelegate))
+                {
+                    // No "add" method for this collection, hence, not supported for deserialization.
+                    throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                        JsonPropertyInfo.DeclaredPropertyType,
+                        JsonPropertyInfo.ParentClassType,
+                        JsonPropertyInfo.PropertyInfo);
+                }
+
+                AddObjectToEnumerable = addMethodDelegate;
+            }
+            else if (targetEnumerable is IList targetList)
+            {
+                if (targetList.IsReadOnly)
+                {
+                    throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    JsonPropertyInfo.DeclaredPropertyType,
+                    JsonPropertyInfo.ParentClassType,
+                    JsonPropertyInfo.PropertyInfo);
+                }
+            }
+            // If there's no add method, and we can't cast to IList, this collection is not supported for deserialization.
+            else
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    JsonPropertyInfo.DeclaredPropertyType,
+                    JsonPropertyInfo.ParentClassType,
+                    JsonPropertyInfo.PropertyInfo);
+            }
+        }
+
+        public void DetermineIfDictionaryCanBePopulated(object targetDictionary)
         {
-            Debug.Assert(ReturnValue == null);
-            ReturnValue = value;
+            if (!JsonPropertyInfo.CanPopulateDictionary(targetDictionary))
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+                    JsonPropertyInfo.DeclaredPropertyType,
+                    JsonPropertyInfo.ParentClassType,
+                    JsonPropertyInfo.PropertyInfo);
+            }
         }
 
         public bool SkipProperty => Drain ||
index 27e9812..baa6bc1 100644 (file)
@@ -54,6 +54,11 @@ namespace System.Text.Json
             return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
         }
 
+        public override Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target)
+        {
+            Debug.Assert(addMethod != null && target != null);
+            return (Action<TProperty>)addMethod.CreateDelegate(typeof(Action<TProperty>), target);
+        }
 
         public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType)
         {
@@ -97,6 +102,14 @@ namespace System.Text.Json
 
         public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType)
         {
+            Debug.Assert(collectionType.IsGenericType);
+
+            // Only string keys are allowed.
+            if (collectionType.GetGenericArguments()[0] != typeof(string))
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null);
+            }
+
             MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType);
 
             if (createRange == null)
index 2214e79..9d16c5b 100644 (file)
@@ -42,6 +42,12 @@ namespace System.Text.Json
             return () => Activator.CreateInstance(type);
         }
 
+        public override Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target)
+        {
+            Debug.Assert(addMethod != null && target != null);
+            return (Action<TProperty>)addMethod.CreateDelegate(typeof(Action<TProperty>), target);
+        }
+
         public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType)
         {
             MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType);
@@ -67,6 +73,14 @@ namespace System.Text.Json
 
         public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType)
         {
+            Debug.Assert(collectionType.IsGenericType);
+
+            // Only string keys are allowed.
+            if (collectionType.GetGenericArguments()[0] != typeof(string))
+            {
+                throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null);
+            }
+
             MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType);
 
             if (createRange == null)
index d734f4b..a9157b2 100644 (file)
@@ -52,13 +52,6 @@ namespace System.Text.Json
                 Current.PopStackOnEndCollection = true;
                 Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
             }
-            else if (classType == ClassType.IDictionaryConstructible)
-            {
-                Current.PopStackOnEndCollection = true;
-                Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
-
-                Current.IsIDictionaryConstructible = true;
-            }
             else
             {
                 Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown);
index 740702f..afa42b7 100644 (file)
@@ -22,8 +22,6 @@ namespace System.Text.Json
         public IEnumerator CollectionEnumerator;
         // Note all bools are kept together for packing:
         public bool PopStackOnEndCollection;
-        public bool IsIDictionaryConstructible;
-        public bool IsIDictionaryConstructibleProperty;
 
         // The current object.
         public bool PopStackOnEndObject;
@@ -38,15 +36,10 @@ namespace System.Text.Json
         public void Initialize(Type type, JsonSerializerOptions options)
         {
             JsonClassInfo = options.GetOrAddClass(type);
-            if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary)
+            if ((JsonClassInfo.ClassType & (ClassType.Value | ClassType.Enumerable | ClassType.Dictionary)) != 0)
             {
                 JsonPropertyInfo = JsonClassInfo.PolicyProperty;
             }
-            else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
-            {
-                JsonPropertyInfo = JsonClassInfo.PolicyProperty;
-                IsIDictionaryConstructible = true;
-            }
         }
 
         public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, JsonSerializerOptions options, bool writeNull = false)
@@ -65,7 +58,7 @@ namespace System.Text.Json
                 Debug.Assert(writeNull == false);
 
                 // Write start without a property name.
-                if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible)
+                if (classType == ClassType.Object || classType == ClassType.Dictionary)
                 {
                     writer.WriteStartObject();
                     StartObjectWritten = true;
@@ -84,9 +77,7 @@ namespace System.Text.Json
             {
                 writer.WriteNull(propertyName);
             }
-            else if (classType == ClassType.Object ||
-                classType == ClassType.Dictionary ||
-                classType == ClassType.IDictionaryConstructible)
+            else if ((classType & (ClassType.Object | ClassType.Dictionary)) != 0)
             {
                 writer.WriteStartObject(propertyName);
                 StartObjectWritten = true;
@@ -103,7 +94,6 @@ namespace System.Text.Json
             CurrentValue = null;
             CollectionEnumerator = null;
             ExtensionDataStatus = ExtensionDataWriteStatus.NotStarted;
-            IsIDictionaryConstructible = false;
             JsonClassInfo = null;
             PropertyEnumeratorIndex = 0;
             PopStackOnEndCollection = false;
@@ -116,7 +106,6 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void EndProperty()
         {
-            IsIDictionaryConstructibleProperty = false;
             JsonPropertyInfo = null;
             KeyName = null;
             MoveToNextProperty = false;
index 680d862..37b2765 100644 (file)
@@ -102,7 +102,7 @@ namespace System.Text.Json.Serialization.Tests
 
             UnsupportedDerivedTypesWrapper_IEnumerable wrapper = new UnsupportedDerivedTypesWrapper_IEnumerable
             {
-                IEnumerableWrapper = new StringIEnumerableWrapper() { "1", "2", "3" },
+                IEnumerableWrapper = new StringIEnumerableWrapper(new List<string> { "1", "2", "3" }),
             };
 
             // Without converter, we throw on deserialize.
index 5be02db..691c3ed 100644 (file)
@@ -888,7 +888,7 @@ namespace System.Text.Json.Serialization.Tests
             {
                 IDictionary ht = new Hashtable();
                 ht.Add("Key", "Value");
-                Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(ht));
+                Assert.Equal(@"{""Key"":""Value""}", JsonSerializer.Serialize(ht));
             }
         }
 
index cf85ed0..aa319b2 100644 (file)
@@ -654,7 +654,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj1));
 
             ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString();
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj2));
+            Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj2));
         }
 
         private class ClassWithExtensionPropertyAlreadyInstantiated
index 4a75f70..cc9f383 100644 (file)
@@ -154,15 +154,31 @@ namespace System.Text.Json.Serialization.Tests
                         }
                     }";
 
-            // Unsupported collections will throw by default.
+            // Unsupported collections will throw on deserialize by default.
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithUnsupportedDictionary>(json));
+            
             // Using new options instance to prevent using previously cached metadata.
             JsonSerializerOptions options = new JsonSerializerOptions();
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options));
+            string serialized = JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options);
+            
+            // Object keys are fine on serialization if the keys are strings.
+            Assert.Contains(@"""MyConcurrentDict"":null", serialized);
+            Assert.Contains(@"""MyIDict"":null", serialized);
+            Assert.Contains(@"""MyDict"":null", serialized);
+
+            // Unsupported collections will throw on deserialize by default.
             options = new JsonSerializerOptions();
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<WrapperForClassWithUnsupportedDictionary>(wrapperJson, options));
+            
             options = new JsonSerializerOptions();
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options));
+            serialized = JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options);
+
+            // Object keys are fine on serialization if the keys are strings.
+            Assert.Contains(@"{""MyClass"":{", serialized);
+            Assert.Contains(@"""MyConcurrentDict"":null", serialized);
+            Assert.Contains(@"""MyIDict"":null", serialized);
+            Assert.Contains(@"""MyDict"":null", serialized);
+            Assert.Contains("}}", serialized);
 
             // When ignored, we can serialize and deserialize without exceptions.
             options = new JsonSerializerOptions();
index cb7b529..d6ffc6c 100644 (file)
@@ -93,7 +93,7 @@ namespace System.Text.Json.Serialization.Tests
         // Call only when testing serialization.
         public void Initialize()
         {
-            MyStringIEnumerableWrapper = new StringIEnumerableWrapper() { "Hello" };
+            MyStringIEnumerableWrapper = new StringIEnumerableWrapper(new List<string>{ "Hello" });
         }
     }
 
@@ -111,7 +111,7 @@ namespace System.Text.Json.Serialization.Tests
         // Call only when testing serialization.
         public void Initialize()
         {
-            MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper() { "Hello" };
+            MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper(new List<string> { "Hello" });
         }
     }
 
@@ -129,7 +129,7 @@ namespace System.Text.Json.Serialization.Tests
         // Call only when testing serialization.
         public void Initialize()
         {
-            MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper() { "Hello" };
+            MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper(new List<string> { "Hello" });
         }
     }
 
@@ -156,10 +156,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<string> _list = new List<string>();
 
-        // For populating test data only. We can't rely on this method for real input.
-        public void Add(string item)
+        public StringIEnumerableWrapper() { }
+
+        public StringIEnumerableWrapper(List<string> items)
         {
-            _list.Add(item);
+            _list = items;
         }
 
         public IEnumerator<string> GetEnumerator()
@@ -177,9 +178,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<T> _list = new List<T>();
 
-        public void Add(T item)
+        public GenericIEnumerableWrapper() { }
+
+        public GenericIEnumerableWrapper(List<T> items)
         {
-            _list.Add(item);
+            _list = items;
         }
 
         public IEnumerator<T> GetEnumerator()
@@ -201,7 +204,7 @@ namespace System.Text.Json.Serialization.Tests
 
         public virtual bool IsReadOnly => ((ICollection<string>)_list).IsReadOnly;
 
-        public void Add(string item)
+        public virtual void Add(string item)
         {
             _list.Add(item);
         }
@@ -252,7 +255,7 @@ namespace System.Text.Json.Serialization.Tests
 
         public virtual bool IsReadOnly => ((IList<string>)_list).IsReadOnly;
 
-        public void Add(string item)
+        public virtual void Add(string item)
         {
             _list.Add(item);
         }
@@ -417,10 +420,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<string> _list = new List<string>();
 
-        // For populating test data only. We cannot assume actual input will have this method.
-        public void Add(string item)
+        public StringIReadOnlyCollectionWrapper() { }
+
+        public StringIReadOnlyCollectionWrapper(List<string> list)
         {
-            _list.Add(item);
+            _list = list;
         }
 
         public int Count => _list.Count;
@@ -440,9 +444,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<T> _list = new List<T>();
 
-        public void Add(T item)
+        public GenericIReadOnlyCollectionWrapper() { }
+
+        public GenericIReadOnlyCollectionWrapper(List<T> list)
         {
-            _list.Add(item);
+            _list = list;
         }
 
         public int Count => _list.Count;
@@ -462,10 +468,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<string> _list = new List<string>();
 
-        // For populating test data only. We cannot assume actual input will have this method.
-        public void Add(string item)
+        public StringIReadOnlyListWrapper() { }
+
+        public StringIReadOnlyListWrapper(List<string> list)
         {
-            _list.Add(item);
+            _list = list;
         }
 
         public string this[int index] => _list[index];
@@ -487,9 +494,11 @@ namespace System.Text.Json.Serialization.Tests
     {
         private readonly List<T> _list = new List<T>();
 
-        public void Add(T item)
+        public GenericIReadOnlyListWrapper() { }
+
+        public GenericIReadOnlyListWrapper(List<T> list)
         {
-            _list.Add(item);
+            _list = list;
         }
 
         public T this[int index] => _list[index];
@@ -726,7 +735,7 @@ namespace System.Text.Json.Serialization.Tests
 
         public virtual bool IsReadOnly => ((IDictionary<string, string>)_dictionary).IsReadOnly;
 
-        public void Add(string key, string value)
+        public virtual void Add(string key, string value)
         {
             ((IDictionary<string, string>)_dictionary).Add(key, value);
         }
@@ -976,6 +985,14 @@ namespace System.Text.Json.Serialization.Tests
 
     public class StringListWrapper : List<string> { }
 
+    class MyMyList<T> : GenericListWrapper<T>
+    {
+    }
+
+    class MyListString : GenericListWrapper<string>
+    {
+    }
+
     public class GenericListWrapper<T> : List<T> { }
 
     public class StringStackWrapper : Stack<string>
index f549043..7952ea6 100644 (file)
@@ -15,6 +15,8 @@ namespace System.Text.Json.Serialization.Tests
         public HashtableWrapper MyHashtableWrapper { get; set; }
         public ArrayListWrapper MyArrayListWrapper { get; set; }
         public SortedListWrapper MySortedListWrapper { get; set; }
+        public StackWrapper MyStackWrapper { get; set; }
+        public QueueWrapper MyQueueWrapper { get; set; }
 
         public static readonly string s_json =
             @"{" +
@@ -22,7 +24,9 @@ namespace System.Text.Json.Serialization.Tests
             @"""MyIDictionaryWrapper"" : {""key"" : ""value""}," +
             @"""MyHashtableWrapper"" : {""key"" : ""value""}," +
             @"""MyArrayListWrapper"" : [""Hello""]," +
-            @"""MySortedListWrapper"" : {""key"" : ""value""}" +
+            @"""MySortedListWrapper"" : {""key"" : ""value""}," +
+            @"""MyStackWrapper"" : [""Hello""]," +
+            @"""MyQueueWrapper"" : [""Hello""]" +
             @"}";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
@@ -34,6 +38,11 @@ namespace System.Text.Json.Serialization.Tests
             MyHashtableWrapper = new HashtableWrapper(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("key", "value" ) });
             MyArrayListWrapper = new ArrayListWrapper() { "Hello" };
             MySortedListWrapper = new SortedListWrapper() { { "key", "value" } };
+            MyStackWrapper = new StackWrapper();
+            MyQueueWrapper = new QueueWrapper();
+
+            MyStackWrapper.Push("Hello");
+            MyQueueWrapper.Enqueue("Hello");
         }
 
         public void Verify()
@@ -43,6 +52,8 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal("value", ((JsonElement)MyHashtableWrapper["key"]).GetString());
             Assert.Equal("Hello", ((JsonElement)MyArrayListWrapper[0]).GetString());
             Assert.Equal("value", ((JsonElement)MySortedListWrapper["key"]).GetString());
+            Assert.Equal("Hello", ((JsonElement)MyStackWrapper.Peek()).GetString());
+            Assert.Equal("Hello", ((JsonElement)MyQueueWrapper.Peek()).GetString());
         }
     }
 
index ab1def8..d07037b 100644 (file)
@@ -1154,5 +1154,17 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyStringICollectionWrapper>(@"[""1"", ""2""]"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyStringToStringIDictionaryWrapper>(@"{""Key"":""key"",""Value"":""value""}"));
         }
+
+        [Fact]
+        public static void Read_HigherOrderCollectionInheritance_Works()
+        {
+            const string json = "[\"test\"]";
+            Assert.Equal("test", JsonSerializer.Deserialize<string[]>(json)[0]);
+            Assert.Equal("test", JsonSerializer.Deserialize<List<string>>(json).First());
+            Assert.Equal("test", JsonSerializer.Deserialize<StringListWrapper>(json).First());
+            Assert.Equal("test", JsonSerializer.Deserialize<GenericListWrapper<string>>(json).First());
+            Assert.Equal("test", JsonSerializer.Deserialize<MyMyList<string>>(json).First());
+            Assert.Equal("test", JsonSerializer.Deserialize<MyListString>(json).First());
+        }
     }
 }
index 8cd21e1..f42fe29 100644 (file)
@@ -304,8 +304,13 @@ namespace System.Text.Json.Serialization.Tests
             }
             Assert.Equal(0, count);
 
-            // TODO: use reflection to support types deriving from Stack.
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StackWrapper>(@"[1,2]"));
+            StackWrapper wrapper =  JsonSerializer.Deserialize<StackWrapper>(@"[1,2]");
+            expected = 2;
+
+            foreach (JsonElement i in wrapper)
+            {
+                Assert.Equal(expected--, i.GetInt32());
+            }
         }
 
         [Fact]
@@ -373,9 +378,14 @@ namespace System.Text.Json.Serialization.Tests
                 count++;
             }
             Assert.Equal(0, count);
+            
+            QueueWrapper wrapper = JsonSerializer.Deserialize<QueueWrapper>(@"[1,2]");
+            expected = 1;
 
-            // TODO: use reflection to support types deriving from Queue.
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<QueueWrapper>(@"[1,2]"));
+            foreach (JsonElement i in wrapper)
+            {
+                Assert.Equal(expected++, i.GetInt32());
+            }
         }
 
         [Fact]
@@ -453,8 +463,6 @@ namespace System.Text.Json.Serialization.Tests
         {
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithIEnumerableWrapper>(SimpleTestClassWithIEnumerableWrapper.s_json));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithICollectionWrapper>(SimpleTestClassWithICollectionWrapper.s_json));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithStackWrapper>(SimpleTestClassWithStackWrapper.s_json));
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithQueueWrapper>(SimpleTestClassWithQueueWrapper.s_json));
         }
     }
 }
index 077eb4c..a803f71 100644 (file)
@@ -77,11 +77,11 @@ namespace System.Text.Json.Serialization.Tests
             string json = JsonSerializer.Serialize(input);
             Assert.Equal("[[1,2],[3,4]]", json);
 
-            GenericIEnumerableWrapper<StringIEnumerableWrapper> input2 = new GenericIEnumerableWrapper<StringIEnumerableWrapper>
+            GenericIEnumerableWrapper<StringIEnumerableWrapper> input2 = new GenericIEnumerableWrapper<StringIEnumerableWrapper>(new List<StringIEnumerableWrapper>
             {
-                new StringIEnumerableWrapper() { "1", "2" },
-                new StringIEnumerableWrapper() { "3", "4" }
-            };
+                new StringIEnumerableWrapper(new List<string> { "1", "2" }),
+                new StringIEnumerableWrapper(new List<string> { "3", "4" }),
+            });
 
             json = JsonSerializer.Serialize(input2);
             Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);
@@ -242,11 +242,11 @@ namespace System.Text.Json.Serialization.Tests
             string json = JsonSerializer.Serialize(input);
             Assert.Equal("[[1,2],[3,4]]", json);
 
-            GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper> input2 = new GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper>
+            GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper> input2 = new GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper>(new List<StringIReadOnlyCollectionWrapper>
             {
-                new StringIReadOnlyCollectionWrapper() { "1", "2" },
-                new StringIReadOnlyCollectionWrapper() { "3", "4" }
-            };
+                new StringIReadOnlyCollectionWrapper(new List<string> { "1", "2" }),
+                new StringIReadOnlyCollectionWrapper(new List<string> { "3", "4" })
+            });
 
             json = JsonSerializer.Serialize(input2);
             Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);
@@ -297,11 +297,11 @@ namespace System.Text.Json.Serialization.Tests
             string json = JsonSerializer.Serialize(input);
             Assert.Equal("[[1,2],[3,4]]", json);
 
-            GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper> input2 = new GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper>
+            GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper> input2 = new GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper>(new List<StringIReadOnlyListWrapper>
             {
-                new StringIReadOnlyListWrapper() { "1", "2" },
-                new StringIReadOnlyListWrapper() { "3", "4" }
-            };
+                new StringIReadOnlyListWrapper(new List<string> { "1", "2" }),
+                new StringIReadOnlyListWrapper(new List<string> { "3", "4" })
+            });
 
             json = JsonSerializer.Serialize(input2);
             Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);