Add [JsonConstructor] and support for deserializing with parameterized ctors (#33444)
authorLayomi Akinrinade <laakinri@microsoft.com>
Fri, 20 Mar 2020 18:29:21 +0000 (11:29 -0700)
committerGitHub <noreply@github.com>
Fri, 20 Mar 2020 18:29:21 +0000 (11:29 -0700)
* Add [JsonConstructor] and support for deserializing with parameterized ctors

* Add some more tests and clean up

* Move property and parameter caches from converter to JsonClassInfo

* Address review feedback

* Address review feedback and reduce regression for existing benchmarks

* Address review feedback and add more tests

* Clean up arg state on ReadStack

50 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArgumentState.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Arguments.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableWithAddMethodConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConstructorAttribute.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfo.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfoOfT.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTTypeToConvert.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/MemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ParameterRef.cs [new file with mode: 0644]
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/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.AttributePresence.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Cache.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Exceptions.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.ParameterMatching.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Stream.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.Constructor.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 135345f..e7ef982 100644 (file)
@@ -753,6 +753,11 @@ namespace System.Text.Json.Serialization
         public abstract T Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options);
         public abstract void Write(System.Text.Json.Utf8JsonWriter writer, [System.Diagnostics.CodeAnalysis.DisallowNull] T value, System.Text.Json.JsonSerializerOptions options);
     }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Constructor, AllowMultiple = false)]
+    public sealed partial class JsonConstructorAttribute : System.Text.Json.Serialization.JsonAttribute
+    {
+        public JsonConstructorAttribute() { }
+    }
     [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
     public sealed partial class JsonExtensionDataAttribute : System.Text.Json.Serialization.JsonAttribute
     {
index 11363d9..167f357 100644 (file)
   <data name="JsonSerializerDoesNotSupportComments" xml:space="preserve">
     <value>Comments cannot be stored when deserializing objects, only the Skip and Disallow comment handling modes are supported.</value>
   </data>
-  <data name="DeserializeMissingParameterlessConstructor" xml:space="preserve">
-    <value>Deserialization of reference types without parameterless constructor is not supported. Type '{0}'</value>
+  <data name="DeserializeMissingDeserializationConstructor" xml:space="preserve">
+    <value>Deserialization of reference types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with '{0}' is not supported. Type '{1}'</value>
   </data>
   <data name="DeserializePolymorphicInterface" xml:space="preserve">
     <value>Deserialization of interface types is not supported. Type '{0}'</value>
   <data name="MetadataInvalidPropertyWithLeadingDollarSign" xml:space="preserve">
     <value>Properties that start with '$' are not allowed on preserve mode, either escape the character or turn off preserve references by setting ReferenceHandling to ReferenceHandling.Default.</value>
   </data>
+  <data name="MultipleMembersBindWithConstructorParameter" xml:space="preserve">
+    <value>Members '{0}' and '{1}' on type '{2}' cannot both bind with parameter '{3}' in constructor '{4}' on deserialization.</value>
+  </data>
+  <data name="ConstructorParamIncompleteBinding" xml:space="preserve">
+    <value>Each parameter in constructor '{0}' on type '{1}' must bind to an object member on deserialization. Each parameter name must be the camel case equivalent of an object member named with the pascal case naming convention.</value>
+  </data>
+  <data name="ConstructorMaxOf64Parameters" xml:space="preserve">
+    <value>The constructor '{0}' on type '{1}' may not have more than 64 parameters for deserialization.</value>
+  </data>
+  <data name="ObjectWithParameterizedCtorRefMetadataNotHonored" xml:space="preserve">
+    <value>Reference metadata is not honored when deserializing types using parameterized constructors. See type '{0}'.</value>
+  </data>
   <data name="SerializerConverterFactoryReturnsNull" xml:space="preserve">
     <value>The converter '{0}' cannot return a null value.</value>
   </data>
   <data name="SerializationNotSupportedParentType" xml:space="preserve">
     <value>The unsupported member type is located on type '{0}'.</value>
   </data>
+  <data name="ExtensionDataCannotBindToCtorParam" xml:space="preserve">
+    <value>The extension data property '{0}' on type '{1}' cannot bind with a parameter in constructor '{2}'.</value>
+  </data>
 </root>
\ No newline at end of file
index 461572b..39786bd 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DocumentationFile>$(OutputPath)$(MSBuildProjectName).xml</DocumentationFile>
@@ -53,7 +53,9 @@
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.MultiSegment.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
+    <Compile Include="System\Text\Json\Serialization\Arguments.cs" />
     <Compile Include="System\Text\Json\Serialization\ClassType.cs" />
+    <Compile Include="System\Text\Json\Serialization\ArgumentState.cs" />
     <Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Collection\ArrayConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Collection\ConcurrentQueueOfTConverter.cs" />
@@ -80,6 +82,9 @@
     <Compile Include="System\Text\Json\Serialization\Converters\Collection\StackOfTConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectConverterFactory.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectDefaultConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.Large.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectWithParameterizedConstructorConverter.Small.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\BooleanConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\ByteArrayConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\ByteConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\DefaultReferenceResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonClassInfo.Cache.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonCollectionConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonConstructorAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverter.ReadAhead.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonObjectConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonParameterInfo.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonParameterInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoOfTTypeToConvert.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyNameAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\MemberAccessor.cs" />
     <Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
     <Compile Include="System\Text\Json\Serialization\PooledByteBufferWriter.cs" />
+    <Compile Include="System\Text\Json\Serialization\ParameterRef.cs" />
     <Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
     <Compile Include="System\Text\Json\Serialization\ReadStack.cs" />
     <Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
index 9a1805c..1d13e73 100644 (file)
@@ -98,5 +98,12 @@ namespace System.Text.Json
         public const int LowSurrogateStartValue = 0xDC00;
         public const int LowSurrogateEndValue = 0xDFFF;
         public const int BitShiftBy10 = 0x400;
+
+        // The maximum number of parameters a constructor can have where it can be considered
+        // for a path on deserialization where we don't box the constructor arguments.
+        public const int UnboxedParameterCountThreshold = 4;
+
+        // The maximum number of parameters a constructor can have where it can be supported.
+        public const int MaxParameterCount = 64;
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArgumentState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArgumentState.cs
new file mode 100644 (file)
index 0000000..71f4d2f
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.Generic;
+
+using FoundProperties = System.ValueTuple<System.Text.Json.JsonPropertyInfo, System.Text.Json.JsonReaderState, long, byte[]?, string?>;
+using FoundPropertiesAsync = System.ValueTuple<System.Text.Json.JsonPropertyInfo, object?, string?>;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Holds relevant state when deserializing objects with parameterized constructors.
+    /// Lives on the current ReadStackFrame.
+    /// </summary>
+    internal class ArgumentState
+    {
+        // Cache for parsed constructor arguments.
+        public object Arguments = null!;
+
+        // When deserializing objects with parameterized ctors, the properties we find on the first pass.
+        public FoundProperties[]? FoundProperties;
+
+        // When deserializing objects with parameterized ctors asynchronously, the properties we find on the first pass.
+        public FoundPropertiesAsync[]? FoundPropertiesAsync;
+        public int FoundPropertyCount;
+
+        // Current constructor parameter value.
+        public JsonParameterInfo? JsonParameterInfo;
+
+        // For performance, we order the parameters by the first deserialize and PropertyIndex helps find the right slot quicker.
+        public int ParameterIndex;
+        public List<ParameterRef>? ParameterRefCache;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Arguments.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Arguments.cs
new file mode 100644 (file)
index 0000000..2d4c737
--- /dev/null
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Constructor arguments for objects with parameterized ctors with less than 5 parameters.
+    /// This is to avoid boxing for small, immutable objects.
+    /// </summary>
+    internal sealed class Arguments<TArg0, TArg1, TArg2, TArg3>
+    {
+        public TArg0 Arg0 = default!;
+        public TArg1 Arg1 = default!;
+        public TArg2 Arg2 = default!;
+        public TArg3 Arg3 = default!;
+    }
+}
index 6760015..7fb4f32 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new List<TElement>();
@@ -37,7 +37,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index 5702bd2..6bc612f 100644 (file)
@@ -32,7 +32,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new Dictionary<string, object>();
@@ -41,7 +41,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index fd912fa..63c329e 100644 (file)
@@ -31,7 +31,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new Dictionary<string, TValue>();
@@ -40,7 +40,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index af72f48..72db89f 100644 (file)
@@ -26,7 +26,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (!TypeToConvert.IsAssignableFrom(RuntimeType))
             {
-                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
             }
 
             state.Current.ReturnValue = new List<object?>();
index 21045a8..8dd7133 100644 (file)
@@ -25,7 +25,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (!TypeToConvert.IsAssignableFrom(RuntimeType))
             {
-                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
             }
 
             state.Current.ReturnValue = new List<TElement>();
index ab11eca..cff243c 100644 (file)
@@ -24,7 +24,7 @@ namespace System.Text.Json.Serialization.Converters
 
             if (constructorDelegate == null)
             {
-                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
             }
 
             state.Current.ReturnValue = constructorDelegate();
index b33a217..cee12dc 100644 (file)
@@ -27,7 +27,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new List<object?>();
@@ -36,7 +36,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index a682d0e..fa61b4a 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new List<TElement>();
@@ -37,7 +37,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index 74b7080..0517525 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (!TypeToConvert.IsAssignableFrom(RuntimeType))
             {
-                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
             }
 
             state.Current.ReturnValue = new Dictionary<string, TValue>();
index 02b77ad..f0fcd78 100644 (file)
@@ -25,7 +25,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 state.Current.ReturnValue = new HashSet<TElement>();
@@ -34,7 +34,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (classInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(TypeToConvert);
                 }
 
                 TCollection returnValue = (TCollection)classInfo.CreateObject()!;
index 431c6fd..8139699 100644 (file)
@@ -5,6 +5,7 @@
 using System.Collections;
 using System.Diagnostics;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -21,16 +22,117 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
+        [PreserveDependency(".ctor", "System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1")]
+        [PreserveDependency(".ctor", "System.Text.Json.Serialization.Converters.LargeObjectWithParameterizedConstructorConverter`1")]
+        [PreserveDependency(".ctor", "System.Text.Json.Serialization.Converters.SmallObjectWithParameterizedConstructorConverter`5")]
         public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
         {
-            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
-                typeof(ObjectDefaultConverter<>).MakeGenericType(typeToConvert),
-                BindingFlags.Instance | BindingFlags.Public,
-                binder: null,
-                args: null,
-                culture: null)!;
+            JsonConverter converter;
+            Type converterType;
 
+            ConstructorInfo? constructor = GetDeserializationConstructor(typeToConvert);
+            ParameterInfo[]? parameters = constructor?.GetParameters();
+
+            if (constructor == null || typeToConvert.IsAbstract || parameters!.Length == 0)
+            {
+                converterType = typeof(ObjectDefaultConverter<>).MakeGenericType(typeToConvert);
+            }
+            else
+            {
+                int parameterCount = parameters.Length;
+
+                if (parameterCount <= JsonConstants.UnboxedParameterCountThreshold)
+                {
+                    Type placeHolderType = typeof(object);
+                    Type[] typeArguments = new Type[JsonConstants.UnboxedParameterCountThreshold + 1];
+
+                    typeArguments[0] = typeToConvert;
+                    for (int i = 0; i < JsonConstants.UnboxedParameterCountThreshold; i++)
+                    {
+                        if (i < parameterCount)
+                        {
+                            typeArguments[i + 1] = parameters[i].ParameterType;
+                        }
+                        else
+                        {
+                            // Use placeholder arguments if there are less args than the threshold.
+                            typeArguments[i + 1] = placeHolderType;
+                        }
+                    }
+
+                    converterType = typeof(SmallObjectWithParameterizedConstructorConverter<,,,,>).MakeGenericType(typeArguments);
+                }
+                else
+                {
+                    converterType = typeof(LargeObjectWithParameterizedConstructorConverter<>).MakeGenericType(typeToConvert);
+                }
+            }
+
+            converter = (JsonConverter)Activator.CreateInstance(
+                    converterType,
+                    BindingFlags.Instance | BindingFlags.Public,
+                    binder: null,
+                    args: null,
+                    culture: null)!;
+
+            converter.ConstructorInfo = constructor!;
             return converter;
         }
+
+        private ConstructorInfo? GetDeserializationConstructor(Type type)
+        {
+            ConstructorInfo? ctorWithAttribute = null;
+            ConstructorInfo? publicParameterlessCtor = null;
+            ConstructorInfo? lonePublicCtor = null;
+
+            ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+
+            if (constructors.Length == 1)
+            {
+                lonePublicCtor = constructors[0];
+            }
+
+            foreach (ConstructorInfo constructor in constructors)
+            {
+                if (constructor.GetCustomAttribute<JsonConstructorAttribute>() != null)
+                {
+                    if (ctorWithAttribute != null)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute<JsonConstructorAttribute>(type);
+                    }
+
+                    ctorWithAttribute = constructor;
+                }
+                else if (constructor.GetParameters().Length == 0)
+                {
+                    publicParameterlessCtor = constructor;
+                }
+            }
+
+            // For correctness, throw if multiple ctors have [JsonConstructor], even if one or more are non-public.
+            ConstructorInfo? dummyCtorWithAttribute = ctorWithAttribute;
+
+            constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
+            foreach (ConstructorInfo constructor in constructors)
+            {
+                if (constructor.GetCustomAttribute<JsonConstructorAttribute>() != null)
+                {
+                    if (dummyCtorWithAttribute != null)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute<JsonConstructorAttribute>(type);
+                    }
+
+                    dummyCtorWithAttribute = constructor;
+                }
+            }
+
+            // Structs will use default constructor if attribute isn't used.
+            if (type.IsValueType && ctorWithAttribute == null)
+            {
+                return null;
+            }
+
+            return ctorWithAttribute ?? publicParameterlessCtor ?? lonePublicCtor;
+        }
     }
 }
index 0c2d139..640088d 100644 (file)
@@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization.Converters
     /// <summary>
     /// Default base class implementation of <cref>JsonObjectConverter{T}</cref>.
     /// </summary>
-    internal sealed class ObjectDefaultConverter<T> : JsonObjectConverter<T> where T : notnull
+    internal class ObjectDefaultConverter<T> : JsonObjectConverter<T> where T : notnull
     {
         internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
         {
@@ -28,7 +28,7 @@ namespace System.Text.Json.Serialization.Converters
 
                 if (state.Current.JsonClassInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(state.Current.JsonClassInfo.Type);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(state.Current.JsonClassInfo.Type);
                 }
 
                 obj = state.Current.JsonClassInfo.CreateObject!()!;
@@ -40,15 +40,14 @@ namespace System.Text.Json.Serialization.Converters
                     reader.ReadWithVerify();
 
                     JsonTokenType tokenType = reader.TokenType;
+
                     if (tokenType == JsonTokenType.EndObject)
                     {
                         break;
                     }
 
-                    if (tokenType != JsonTokenType.PropertyName)
-                    {
-                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
-                    }
+                    // Read method would have thrown if otherwise.
+                    Debug.Assert(tokenType == JsonTokenType.PropertyName);
 
                     JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
                         obj,
@@ -57,28 +56,7 @@ namespace System.Text.Json.Serialization.Converters
                         ref state,
                         out bool useExtensionProperty);
 
-                    // Skip the property if not found.
-                    if (!jsonPropertyInfo.ShouldDeserialize)
-                    {
-                        reader.Skip();
-                        state.Current.EndProperty();
-                        continue;
-                    }
-
-                    // Set the property value.
-                    reader.ReadWithVerify();
-
-                    if (!useExtensionProperty)
-                    {
-                        jsonPropertyInfo.ReadJsonAndSetMember(obj, ref state, ref reader);
-                    }
-                    else
-                    {
-                        jsonPropertyInfo.ReadJsonAndAddExtensionProperty(obj, ref state, ref reader);
-                    }
-
-                    // Ensure any exception thrown in the next read does not have a property in its JsonPath.
-                    state.Current.EndProperty();
+                    ReadPropertyValue(obj, ref state, ref reader, jsonPropertyInfo, useExtensionProperty);
                 }
             }
             else
@@ -122,7 +100,7 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     if (state.Current.JsonClassInfo.CreateObject == null)
                     {
-                        ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(state.Current.JsonClassInfo.Type);
+                        ThrowHelper.ThrowNotSupportedException_DeserializeNoDeserializationConstructor(state.Current.JsonClassInfo.Type);
                     }
 
                     obj = state.Current.JsonClassInfo.CreateObject!()!;
@@ -169,13 +147,11 @@ namespace System.Text.Json.Serialization.Converters
                         JsonTokenType tokenType = reader.TokenType;
                         if (tokenType == JsonTokenType.EndObject)
                         {
-                            // We are done reading properties.
                             break;
                         }
-                        else if (tokenType != JsonTokenType.PropertyName)
-                        {
-                            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
-                        }
+
+                        // Read method would have thrown if otherwise.
+                        Debug.Assert(tokenType == JsonTokenType.PropertyName);
 
                         jsonPropertyInfo = JsonSerializer.LookupProperty(
                             obj,
@@ -207,27 +183,11 @@ namespace System.Text.Json.Serialization.Converters
                             continue;
                         }
 
-                        // Returning false below will cause the read-ahead functionality to finish the read.
-                        state.Current.PropertyState = StackFramePropertyState.ReadValue;
-
-                        if (!state.Current.UseExtensionProperty)
+                        if (!ReadAheadPropertyValue(ref state, ref reader, jsonPropertyInfo))
                         {
-                            if (!SingleValueReadWithReadAhead(jsonPropertyInfo.ConverterBase.ClassType, ref reader, ref state))
-                            {
-                                state.Current.ReturnValue = obj;
-                                value = default;
-                                return false;
-                            }
-                        }
-                        else
-                        {
-                            // The actual converter is JsonElement, so force a read-ahead.
-                            if (!SingleValueReadWithReadAhead(ClassType.Value, ref reader, ref state))
-                            {
-                                state.Current.ReturnValue = obj;
-                                value = default;
-                                return false;
-                            }
+                            state.Current.ReturnValue = obj;
+                            value = default;
+                            return false;
                         }
                     }
 
@@ -270,7 +230,7 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state)
+        internal sealed override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state)
         {
             // Minimize boxing for structs by only boxing once here
             object objectValue = value!;
@@ -396,5 +356,60 @@ namespace System.Text.Json.Serialization.Converters
                 return true;
             }
         }
+
+        protected void ReadPropertyValue(
+            object obj,
+            ref ReadStack state,
+            ref Utf8JsonReader reader,
+            JsonPropertyInfo jsonPropertyInfo,
+            bool useExtensionProperty)
+        {
+            // Skip the property if not found.
+            if (!jsonPropertyInfo.ShouldDeserialize)
+            {
+                reader.Skip();
+            }
+            else
+            {
+                // Set the property value.
+                reader.ReadWithVerify();
+
+                if (!useExtensionProperty)
+                {
+                    jsonPropertyInfo.ReadJsonAndSetMember(obj, ref state, ref reader);
+                }
+                else
+                {
+                    jsonPropertyInfo.ReadJsonAndAddExtensionProperty(obj, ref state, ref reader);
+                }
+            }
+
+            // Ensure any exception thrown in the next read does not have a property in its JsonPath.
+            state.Current.EndProperty();
+        }
+
+        protected bool ReadAheadPropertyValue(ref ReadStack state, ref Utf8JsonReader reader, JsonPropertyInfo jsonPropertyInfo)
+        {
+            // Returning false below will cause the read-ahead functionality to finish the read.
+            state.Current.PropertyState = StackFramePropertyState.ReadValue;
+
+            if (!state.Current.UseExtensionProperty)
+            {
+                if (!SingleValueReadWithReadAhead(jsonPropertyInfo.ConverterBase.ClassType, ref reader, ref state))
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                // The actual converter is JsonElement, so force a read-ahead.
+                if (!SingleValueReadWithReadAhead(ClassType.Value, ref reader, ref state))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs
new file mode 100644 (file)
index 0000000..f4fabaf
--- /dev/null
@@ -0,0 +1,64 @@
+// 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.Buffers;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    /// <summary>
+    /// Implementation of <cref>JsonObjectConverter{T}</cref> that supports the deserialization
+    /// of JSON objects using parameterized constructors.
+    /// </summary>
+    internal sealed class LargeObjectWithParameterizedConstructorConverter<T> : ObjectWithParameterizedConstructorConverter<T> where T : notnull
+    {
+        private JsonClassInfo.ParameterizedConstructorDelegate<T>? _createObject;
+
+        internal override void CreateConstructorDelegate(JsonSerializerOptions options)
+        {
+            _createObject = options.MemberAccessorStrategy.CreateParameterizedConstructor<T>(ConstructorInfo)!;
+        }
+
+        protected override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo)
+        {
+            bool success = jsonParameterInfo.ReadJson(ref state, ref reader, out object? arg0);
+
+            if (success)
+            {
+                ((object[])state.Current.CtorArgumentState!.Arguments!)[jsonParameterInfo.Position] = arg0!;
+            }
+
+            return success;
+        }
+
+        protected override object CreateObject(ref ReadStackFrame frame)
+        {
+            object[] arguments = (object[])frame.CtorArgumentState!.Arguments!;
+
+            if (_createObject == null)
+            {
+                // This means this constructor has more than 64 parameters.
+                ThrowHelper.ThrowNotSupportedException_ConstructorMaxOf64Parameters(ConstructorInfo, TypeToConvert);
+            }
+
+            object obj = _createObject(arguments)!;
+
+            ArrayPool<object>.Shared.Return(arguments, clearArray: true);
+            return obj;
+        }
+
+        protected override void InitializeConstructorArgumentCaches(ref ReadStack state, JsonSerializerOptions options)
+        {
+            object[] arguments = ArrayPool<object>.Shared.Rent(state.Current.JsonClassInfo.ParameterCount);
+            foreach (JsonParameterInfo jsonParameterInfo in state.Current.JsonClassInfo.ParameterCache!.Values)
+            {
+                if (jsonParameterInfo.ShouldDeserialize)
+                {
+                    arguments[jsonParameterInfo.Position] = jsonParameterInfo.DefaultValue!;
+                }
+            }
+
+            state.Current.CtorArgumentState!.Arguments = arguments;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs
new file mode 100644 (file)
index 0000000..fb852b8
--- /dev/null
@@ -0,0 +1,107 @@
+// 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.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    /// <summary>
+    /// Implementation of <cref>JsonObjectConverter{T}</cref> that supports the deserialization
+    /// of JSON objects using parameterized constructors.
+    /// </summary>
+    internal sealed class SmallObjectWithParameterizedConstructorConverter<T, TArg0, TArg1, TArg2, TArg3> : ObjectWithParameterizedConstructorConverter<T> where T : notnull
+    {
+        private JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>? _createObject;
+
+        internal override void CreateConstructorDelegate(JsonSerializerOptions options)
+        {
+            _createObject = options.MemberAccessorStrategy.CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>(ConstructorInfo)!;
+        }
+
+        protected override object CreateObject(ref ReadStackFrame frame)
+        {
+            var arguments = (Arguments<TArg0, TArg1, TArg2, TArg3>)frame.CtorArgumentState!.Arguments!;
+            return _createObject!(arguments.Arg0, arguments.Arg1, arguments.Arg2, arguments.Arg3)!;
+        }
+
+        protected override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo)
+        {
+            Debug.Assert(state.Current.CtorArgumentState!.Arguments != null);
+            var arguments = (Arguments<TArg0, TArg1, TArg2, TArg3>)state.Current.CtorArgumentState.Arguments;
+
+            bool success;
+
+            switch (jsonParameterInfo.Position)
+            {
+                case 0:
+                    success = ((JsonParameterInfo<TArg0>)jsonParameterInfo).ReadJsonTyped(ref state, ref reader, out TArg0 arg0);
+                    if (success)
+                    {
+                        arguments.Arg0 = arg0;
+                    }
+                    break;
+                case 1:
+                    success = ((JsonParameterInfo<TArg1>)jsonParameterInfo).ReadJsonTyped(ref state, ref reader, out TArg1 arg1);
+                    if (success)
+                    {
+                        arguments.Arg1 = arg1;
+                    }
+                    break;
+                case 2:
+                    success = ((JsonParameterInfo<TArg2>)jsonParameterInfo).ReadJsonTyped(ref state, ref reader, out TArg2 arg2);
+                    if (success)
+                    {
+                        arguments.Arg2 = arg2;
+                    }
+                    break;
+                case 3:
+                    success = ((JsonParameterInfo<TArg3>)jsonParameterInfo).ReadJsonTyped(ref state, ref reader, out TArg3 arg3);
+                    if (success)
+                    {
+                        arguments.Arg3 = arg3;
+                    }
+                    break;
+                default:
+                    Debug.Fail("This should never happen.");
+                    throw new InvalidOperationException();
+            }
+
+            return success;
+        }
+
+        protected override void InitializeConstructorArgumentCaches(ref ReadStack state, JsonSerializerOptions options)
+        {
+            var arguments = new Arguments<TArg0, TArg1, TArg2, TArg3>();
+
+            foreach (JsonParameterInfo parameterInfo in state.Current.JsonClassInfo.ParameterCache!.Values)
+            {
+                if (parameterInfo.ShouldDeserialize)
+                {
+                    int position = parameterInfo.Position;
+
+                    switch (position)
+                    {
+                        case 0:
+                            arguments.Arg0 = ((JsonParameterInfo<TArg0>)parameterInfo).TypedDefaultValue!;
+                            break;
+                        case 1:
+                            arguments.Arg1 = ((JsonParameterInfo<TArg1>)parameterInfo).TypedDefaultValue!;
+                            break;
+                        case 2:
+                            arguments.Arg2 = ((JsonParameterInfo<TArg2>)parameterInfo).TypedDefaultValue!;
+                            break;
+                        case 3:
+                            arguments.Arg3 = ((JsonParameterInfo<TArg3>)parameterInfo).TypedDefaultValue!;
+                            break;
+                        default:
+                            Debug.Fail("We should never get here.");
+                            break;
+                    }
+                }
+            }
+
+            state.Current.CtorArgumentState!.Arguments = arguments;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
new file mode 100644 (file)
index 0000000..4042412
--- /dev/null
@@ -0,0 +1,496 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+using FoundProperties = System.ValueTuple<System.Text.Json.JsonPropertyInfo, System.Text.Json.JsonReaderState, long, byte[]?, string?>;
+using FoundPropertiesAsync = System.ValueTuple<System.Text.Json.JsonPropertyInfo, object?, string?>;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    /// <summary>
+    /// Implementation of <cref>JsonObjectConverter{T}</cref> that supports the deserialization
+    /// of JSON objects using parameterized constructors.
+    /// </summary>
+    internal abstract partial class ObjectWithParameterizedConstructorConverter<T> : ObjectDefaultConverter<T> where T : notnull
+    {
+        internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
+        {
+            bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();
+            object obj;
+
+            if (!state.SupportContinuation && !shouldReadPreservedReferences)
+            {
+                // Fast path that avoids maintaining state variables and dealing with preserved references.
+
+                ReadOnlySpan<byte> originalSpan = reader.OriginalSpan;
+
+                ReadConstructorArguments(ref state, ref reader, options);
+
+                obj = CreateObject(ref state.Current);
+
+                if (state.Current.PropertyIndex > 0)
+                {
+                    Utf8JsonReader tempReader;
+
+                    for (int i = 0; i < state.Current.PropertyIndex; i++)
+                    {
+                        JsonPropertyInfo jsonPropertyInfo = state.Current.CtorArgumentState!.FoundProperties![i].Item1;
+                        long resumptionByteIndex = state.Current.CtorArgumentState.FoundProperties[i].Item3;
+                        byte[]? propertyNameArray = state.Current.CtorArgumentState.FoundProperties[i].Item4;
+                        string? dataExtKey = state.Current.CtorArgumentState.FoundProperties[i].Item5;
+
+                        tempReader = new Utf8JsonReader(
+                            originalSpan.Slice(checked((int)resumptionByteIndex)),
+                            isFinalBlock: true,
+                            state: state.Current.CtorArgumentState.FoundProperties[i].Item2);
+
+                        Debug.Assert(tempReader.TokenType == JsonTokenType.PropertyName);
+
+                        state.Current.JsonPropertyName = propertyNameArray;
+                        state.Current.JsonPropertyInfo = jsonPropertyInfo;
+
+                        bool useExtensionProperty = dataExtKey != null;
+
+                        if (useExtensionProperty)
+                        {
+                            Debug.Assert(jsonPropertyInfo == state.Current.JsonClassInfo.DataExtensionProperty);
+                            state.Current.JsonPropertyNameAsString = dataExtKey;
+                            JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo);
+                        }
+
+                        ReadPropertyValue(obj, ref state, ref tempReader, jsonPropertyInfo, useExtensionProperty);
+                    }
+
+                    ArrayPool<FoundProperties>.Shared.Return(state.Current.CtorArgumentState!.FoundProperties!, clearArray: true);
+                }
+            }
+            else
+            {
+                // Slower path that supports continuation and preserved references.
+
+                if (state.Current.ObjectState == StackFrameObjectState.None)
+                {
+                    state.Current.ObjectState = StackFrameObjectState.StartToken;
+                    BeginRead(ref state, ref reader,  options);
+                }
+
+                if (!ReadConstructorArgumentsWithContinuation(ref state, ref reader, options))
+                {
+                    value = default;
+                    return false;
+                }
+
+                obj = CreateObject(ref state.Current);
+
+                if (state.Current.CtorArgumentState!.FoundPropertyCount > 0)
+                {
+                    // Set the properties we've parsed so far.
+                    for (int i = 0; i < state.Current.CtorArgumentState!.FoundPropertyCount; i++)
+                    {
+                        JsonPropertyInfo jsonPropertyInfo = state.Current.CtorArgumentState!.FoundPropertiesAsync![i].Item1;
+                        object? propValue = state.Current.CtorArgumentState!.FoundPropertiesAsync![i].Item2;
+                        string? dataExtKey = state.Current.CtorArgumentState!.FoundPropertiesAsync![i].Item3;
+
+                        if (dataExtKey == null)
+                        {
+                            jsonPropertyInfo.SetValueAsObject(obj, propValue);
+                        }
+                        else
+                        {
+                            Debug.Assert(jsonPropertyInfo == state.Current.JsonClassInfo.DataExtensionProperty);
+
+                            JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo);
+                            object extDictionary = jsonPropertyInfo.GetValueAsObject(obj)!;
+
+                            if (extDictionary is IDictionary<string, JsonElement> dict)
+                            {
+                                dict[dataExtKey] = (JsonElement)propValue!;
+                            }
+                            else
+                            {
+                                ((IDictionary<string, object>)extDictionary)[dataExtKey] = propValue!;
+                            }
+                        }
+                    }
+
+                    ArrayPool<FoundPropertiesAsync>.Shared.Return(state.Current.CtorArgumentState!.FoundPropertiesAsync!, clearArray: true);
+                }
+            }
+
+            // Check if we are trying to build the sorted cache.
+            if (state.Current.PropertyRefCache != null)
+            {
+                state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current);
+            }
+
+            // Check if we are trying to build the sorted parameter cache.
+            if (state.Current.CtorArgumentState!.ParameterRefCache != null)
+            {
+                state.Current.JsonClassInfo.UpdateSortedParameterCache(ref state.Current);
+            }
+
+            value = (T)obj;
+
+            return true;
+        }
+
+        protected abstract void InitializeConstructorArgumentCaches(ref ReadStack state, JsonSerializerOptions options);
+
+        protected abstract bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo);
+
+        protected abstract object CreateObject(ref ReadStackFrame frame);
+
+        /// <summary>
+        /// Performs a full first pass of the JSON input and deserializes the ctor args.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ReadConstructorArguments(ref ReadStack state, ref Utf8JsonReader reader, JsonSerializerOptions options)
+        {
+            BeginRead(ref state, ref reader, options);
+
+            while (true)
+            {
+                // Read the next property name or EndObject.
+                reader.ReadWithVerify();
+
+                JsonTokenType tokenType = reader.TokenType;
+
+                if (tokenType == JsonTokenType.EndObject)
+                {
+                    return;
+                }
+
+                // Read method would have thrown if otherwise.
+                Debug.Assert(tokenType == JsonTokenType.PropertyName);
+
+                if (TryLookupConstructorParameter(ref state, ref reader, options, out JsonParameterInfo? jsonParameterInfo))
+                {
+                    // Set the property value.
+                    reader.ReadWithVerify();
+
+                    if (!(jsonParameterInfo!.ShouldDeserialize))
+                    {
+                        reader.TrySkip();
+                        state.Current.EndConstructorParameter();
+                        continue;
+                    }
+
+                    ReadAndCacheConstructorArgument(ref state, ref reader, jsonParameterInfo);
+
+                    state.Current.EndConstructorParameter();
+                }
+                else
+                {
+                    JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
+                        obj: null!,
+                        ref reader,
+                        options,
+                        ref state,
+                        out _,
+                        createExtensionProperty: false);
+
+                    if (state.Current.CtorArgumentState!.FoundProperties == null)
+                    {
+                        state.Current.CtorArgumentState.FoundProperties =
+                            ArrayPool<FoundProperties>.Shared.Rent(Math.Max(1, state.Current.JsonClassInfo.PropertyCache!.Count));
+                    }
+                    else if (state.Current.PropertyIndex - 1 == state.Current.CtorArgumentState.FoundProperties!.Length)
+                    {
+                        // Rare case where we can't fit all the JSON properties in the rented pool; we have to grow.
+                        // This could happen if there are duplicate properties in the JSON.
+
+                        var newCache = ArrayPool<FoundProperties>.Shared.Rent(state.Current.CtorArgumentState.FoundProperties!.Length * 2);
+
+                        state.Current.CtorArgumentState.FoundProperties!.CopyTo(newCache, 0);
+
+                        ArrayPool<FoundProperties>.Shared.Return(state.Current.CtorArgumentState.FoundProperties!, clearArray: true);
+
+                        state.Current.CtorArgumentState.FoundProperties = newCache!;
+                    }
+
+                    state.Current.CtorArgumentState!.FoundProperties![state.Current.PropertyIndex - 1] = (
+                        jsonPropertyInfo,
+                        reader.CurrentState,
+                        reader.BytesConsumed,
+                        state.Current.JsonPropertyName,
+                        state.Current.JsonPropertyNameAsString);
+
+                    reader.Skip();
+
+                    state.Current.EndProperty();
+                }
+            }
+        }
+
+        private bool ReadConstructorArgumentsWithContinuation(ref ReadStack state, ref Utf8JsonReader reader, JsonSerializerOptions options)
+        {
+            // Process all properties.
+            while (true)
+            {
+                // Determine the property.
+                if (state.Current.PropertyState == StackFramePropertyState.None)
+                {
+                    state.Current.PropertyState = StackFramePropertyState.ReadName;
+
+                    if (!reader.Read())
+                    {
+                        // The read-ahead functionality will do the Read().
+                        return false;
+                    }
+                }
+
+                JsonParameterInfo? jsonParameterInfo;
+                JsonPropertyInfo? jsonPropertyInfo;
+
+                if (state.Current.PropertyState < StackFramePropertyState.Name)
+                {
+                    state.Current.PropertyState = StackFramePropertyState.Name;
+
+                    JsonTokenType tokenType = reader.TokenType;
+
+                    if (tokenType == JsonTokenType.EndObject)
+                    {
+                        return true;
+                    }
+
+                    // Read method would have thrown if otherwise.
+                    Debug.Assert(tokenType == JsonTokenType.PropertyName);
+
+                    if (TryLookupConstructorParameter(
+                        ref state,
+                        ref reader,
+                        options,
+                        out jsonParameterInfo))
+                    {
+                        jsonPropertyInfo = null;
+                    }
+                    else
+                    {
+                        jsonPropertyInfo = JsonSerializer.LookupProperty(
+                            obj: null!,
+                            ref reader,
+                            options,
+                            ref state,
+                            out bool useExtensionProperty,
+                            createExtensionProperty: false);
+
+                        state.Current.UseExtensionProperty = useExtensionProperty;
+                    }
+                }
+                else
+                {
+                    jsonParameterInfo = state.Current.CtorArgumentState!.JsonParameterInfo;
+                    jsonPropertyInfo = state.Current.JsonPropertyInfo;
+                }
+
+                if (jsonParameterInfo != null)
+                {
+                    Debug.Assert(jsonPropertyInfo == null);
+
+                    if (!HandleConstructorArgumentWithContinuation(ref state, ref reader, jsonParameterInfo))
+                    {
+                        return false;
+                    }
+                }
+                else
+                {
+                    if (!HandlePropertyWithContinuation(ref state, ref reader, jsonPropertyInfo!))
+                    {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HandleConstructorArgumentWithContinuation(
+            ref ReadStack state,
+            ref Utf8JsonReader reader,
+            JsonParameterInfo jsonParameterInfo)
+        {
+            if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
+            {
+                if (!jsonParameterInfo.ShouldDeserialize)
+                {
+                    if (!reader.TrySkip())
+                    {
+                        return false;
+                    }
+
+                    state.Current.EndConstructorParameter();
+                    return true;
+                }
+
+                // Returning false below will cause the read-ahead functionality to finish the read.
+                state.Current.PropertyState = StackFramePropertyState.ReadValue;
+
+                if (!SingleValueReadWithReadAhead(jsonParameterInfo.ConverterBase.ClassType, ref reader, ref state))
+                {
+                    return false;
+                }
+            }
+
+            if (!ReadAndCacheConstructorArgument(ref state, ref reader, jsonParameterInfo))
+            {
+                return false;
+            }
+
+            state.Current.EndConstructorParameter();
+            return true;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HandlePropertyWithContinuation(
+            ref ReadStack state,
+            ref Utf8JsonReader reader,
+            JsonPropertyInfo jsonPropertyInfo)
+        {
+            if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
+            {
+                if (!jsonPropertyInfo.ShouldDeserialize)
+                {
+                    if (!reader.TrySkip())
+                    {
+                        return false;
+                    }
+
+                    state.Current.EndProperty();
+                    return true;
+                }
+
+                if (!ReadAheadPropertyValue(ref state, ref reader, jsonPropertyInfo))
+                {
+                    return false;
+                }
+            }
+
+            object? propValue;
+
+            if (state.Current.UseExtensionProperty)
+            {
+                if (!jsonPropertyInfo.ReadJsonExtensionDataValue(ref state, ref reader, out propValue))
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                if (!jsonPropertyInfo.ReadJsonAsObject(ref state, ref reader, out propValue))
+                {
+                    return false;
+                }
+            }
+
+            // Ensure that the cache has enough capacity to add this property.
+
+            if (state.Current.CtorArgumentState!.FoundPropertiesAsync == null)
+            {
+                state.Current.CtorArgumentState.FoundPropertiesAsync =
+                    ArrayPool<FoundPropertiesAsync>.Shared.Rent(Math.Max(1, state.Current.JsonClassInfo.PropertyCache!.Count));
+            }
+            else if (state.Current.CtorArgumentState.FoundPropertyCount == state.Current.CtorArgumentState.FoundPropertiesAsync!.Length)
+            {
+                // Rare case where we can't fit all the JSON properties in the rented pool; we have to grow.
+                // This could happen if there are duplicate properties in the JSON.
+                var newCache = ArrayPool<FoundPropertiesAsync>.Shared.Rent(
+                    state.Current.CtorArgumentState.FoundPropertiesAsync!.Length * 2);
+
+                state.Current.CtorArgumentState.FoundPropertiesAsync!.CopyTo(newCache, 0);
+
+                ArrayPool<FoundPropertiesAsync>.Shared.Return(
+                    state.Current.CtorArgumentState.FoundPropertiesAsync!, clearArray: true);
+
+                state.Current.CtorArgumentState.FoundPropertiesAsync = newCache!;
+            }
+
+            // Cache the property name and value.
+            state.Current.CtorArgumentState.FoundPropertiesAsync[state.Current.CtorArgumentState.FoundPropertyCount++] = (
+                jsonPropertyInfo,
+                propValue,
+                state.Current.JsonPropertyNameAsString);
+
+            state.Current.EndProperty();
+            return true;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void BeginRead(ref ReadStack state, ref Utf8JsonReader reader, JsonSerializerOptions options)
+        {
+            if (reader.TokenType != JsonTokenType.StartObject)
+            {
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+            }
+
+            if (state.Current.JsonClassInfo.ParameterCount != state.Current.JsonClassInfo.ParameterCache!.Count)
+            {
+                ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(ConstructorInfo, TypeToConvert);
+            }
+
+            // Set current JsonPropertyInfo to null to avoid conflicts on push.
+            state.Current.JsonPropertyInfo = null;
+
+            Debug.Assert(state.Current.CtorArgumentState != null);
+
+            InitializeConstructorArgumentCaches(ref state, options);
+        }
+
+        /// <summary>
+        /// Lookup the constructor parameter given its name in the reader.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool TryLookupConstructorParameter(
+            ref ReadStack state,
+            ref Utf8JsonReader reader,
+            JsonSerializerOptions options,
+            out JsonParameterInfo? jsonParameterInfo)
+        {
+            Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
+
+            ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options);
+
+            if (!state.Current.JsonClassInfo.TryGetParameter(unescapedPropertyName, ref state.Current, out jsonParameterInfo))
+            {
+                return false;
+            }
+
+            Debug.Assert(jsonParameterInfo != null);
+
+            // Increment ConstructorParameterIndex so GetProperty() starts with the next parameter the next time this function is called.
+            state.Current.CtorArgumentState!.ParameterIndex++;
+
+            // Support JsonException.Path.
+            Debug.Assert(
+                jsonParameterInfo.JsonPropertyName == null ||
+                options.PropertyNameCaseInsensitive ||
+                unescapedPropertyName.SequenceEqual(jsonParameterInfo.JsonPropertyName));
+
+            if (jsonParameterInfo.JsonPropertyName == null)
+            {
+                byte[] propertyNameArray = unescapedPropertyName.ToArray();
+                if (options.PropertyNameCaseInsensitive)
+                {
+                    // Each payload can have a different name here; remember the value on the temporary stack.
+                    state.Current.JsonPropertyName = propertyNameArray;
+                }
+                else
+                {
+                    //Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
+                    // so it will match the incoming payload except when case insensitivity is enabled(which is handled above).
+                    jsonParameterInfo.JsonPropertyName = propertyNameArray;
+                }
+            }
+
+            state.Current.CtorArgumentState.JsonParameterInfo = jsonParameterInfo;
+
+            return true;
+        }
+
+        internal override bool ConstructorIsParameterized => true;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
deleted file mode 100644 (file)
index a5d3e98..0000000
+++ /dev/null
@@ -1,78 +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.Reflection;
-using System.Text.Json.Serialization;
-
-namespace System.Text.Json
-{
-    internal partial class JsonClassInfo
-    {
-        private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
-        {
-            bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
-            if (hasIgnoreAttribute)
-            {
-                return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
-            }
-
-            JsonConverter converter = GetConverter(
-                propertyType,
-                parentClassType,
-                propertyInfo,
-                out Type runtimeType,
-                options);
-
-            return CreateProperty(
-                declaredPropertyType: propertyType,
-                runtimePropertyType: runtimeType,
-                propertyInfo,
-                parentClassType,
-                converter,
-                options);
-        }
-
-        internal static JsonPropertyInfo CreateProperty(
-            Type declaredPropertyType,
-            Type? runtimePropertyType,
-            PropertyInfo? propertyInfo,
-            Type parentClassType,
-            JsonConverter converter,
-            JsonSerializerOptions options)
-        {
-            // Create the JsonPropertyInfo instance.
-            JsonPropertyInfo jsonPropertyInfo = converter.CreateJsonPropertyInfo();
-
-            jsonPropertyInfo.Initialize(
-                parentClassType,
-                declaredPropertyType,
-                runtimePropertyType,
-                runtimeClassType: converter.ClassType,
-                propertyInfo,
-                converter,
-                options);
-
-            return jsonPropertyInfo;
-        }
-
-        /// <summary>
-        /// Create a <see cref="JsonPropertyInfo"/> for a given Type.
-        /// See <seealso cref="JsonClassInfo.PropertyInfoForClassInfo"/>.
-        /// </summary>
-        internal static JsonPropertyInfo CreatePropertyInfoForClassInfo(
-            Type declaredPropertyType,
-            Type runtimePropertyType,
-            JsonConverter converter,
-            JsonSerializerOptions options)
-        {
-            return CreateProperty(
-                declaredPropertyType: declaredPropertyType,
-                runtimePropertyType: runtimePropertyType,
-                propertyInfo: null, // Not a real property so this is null.
-                parentClassType: typeof(object), // a dummy value (not used)
-                converter : converter,
-                options);
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs
new file mode 100644 (file)
index 0000000..2f8dd30
--- /dev/null
@@ -0,0 +1,580 @@
+// 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.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json
+{
+    [DebuggerDisplay("ClassType.{ClassType}, {Type.Name}")]
+    internal sealed partial class JsonClassInfo
+    {
+        // The length of the property name embedded in the key (in bytes).
+        // The key is a ulong (8 bytes) containing the first 7 bytes of the property name
+        // followed by a byte representing the length.
+        private const int PropertyNameKeyLength = 7;
+
+        // The limit to how many constructor parameter names from the JSON are cached in _parameterRefsSorted before using _parameterCache.
+        private const int ParameterNameCountCacheThreshold = 32;
+
+        // The limit to how many property names from the JSON are cached in _propertyRefsSorted before using PropertyCache.
+        private const int PropertyNameCountCacheThreshold = 64;
+
+        // The number of parameters the deserialization constructor has. If this is not equal to ParameterCache.Count, this means
+        // that not all parameters are bound to object properties, and an exception will be thrown if deserialization is attempted.
+        public int ParameterCount { get; private set; }
+
+        // All of the serializable parameters on a POCO constructor keyed on parameter name.
+        // Only paramaters which bind to properties are cached.
+        public volatile Dictionary<string, JsonParameterInfo>? ParameterCache;
+
+        // All of the serializable properties on a POCO (except the optional extension property) keyed on property name.
+        public volatile Dictionary<string, JsonPropertyInfo>? PropertyCache;
+
+        // All of the serializable properties on a POCO including the optional extension property.
+        // Used for performance during serialization instead of 'PropertyCache' above.
+        public volatile JsonPropertyInfo[]? PropertyCacheArray;
+
+        // Fast cache of constructor parameters by first JSON ordering; may not contain all parameters. Accessed before ParameterCache.
+        // Use an array (instead of List<T>) for highest performance.
+        private volatile ParameterRef[]? _parameterRefsSorted;
+
+        // Fast cache of properties by first JSON ordering; may not contain all properties. Accessed before PropertyCache.
+        // Use an array (instead of List<T>) for highest performance.
+        private volatile PropertyRef[]? _propertyRefsSorted;
+
+        private Dictionary<string, JsonPropertyInfo> CreatePropertyCache(int capacity)
+        {
+            StringComparer comparer;
+
+            if (Options.PropertyNameCaseInsensitive)
+            {
+                comparer = StringComparer.OrdinalIgnoreCase;
+            }
+            else
+            {
+                comparer = StringComparer.Ordinal;
+            }
+
+            return new Dictionary<string, JsonPropertyInfo>(capacity, comparer);
+        }
+
+        public Dictionary<string, JsonParameterInfo> CreateParameterCache(int capacity, JsonSerializerOptions options)
+        {
+            if (options.PropertyNameCaseInsensitive)
+            {
+                return new Dictionary<string, JsonParameterInfo>(capacity, StringComparer.OrdinalIgnoreCase);
+            }
+            else
+            {
+                return new Dictionary<string, JsonParameterInfo>(capacity);
+            }
+        }
+
+        public static JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
+        {
+            bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
+            if (hasIgnoreAttribute)
+            {
+                return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
+            }
+
+            JsonConverter converter = GetConverter(
+                propertyType,
+                parentClassType,
+                propertyInfo,
+                out Type runtimeType,
+                options);
+
+            return CreateProperty(
+                declaredPropertyType: propertyType,
+                runtimePropertyType: runtimeType,
+                propertyInfo,
+                parentClassType,
+                converter,
+                options);
+        }
+
+        internal static JsonPropertyInfo CreateProperty(
+            Type declaredPropertyType,
+            Type? runtimePropertyType,
+            PropertyInfo? propertyInfo,
+            Type parentClassType,
+            JsonConverter converter,
+            JsonSerializerOptions options)
+        {
+            // Create the JsonPropertyInfo instance.
+            JsonPropertyInfo jsonPropertyInfo = converter.CreateJsonPropertyInfo();
+
+            jsonPropertyInfo.Initialize(
+                parentClassType,
+                declaredPropertyType,
+                runtimePropertyType,
+                runtimeClassType: converter.ClassType,
+                propertyInfo,
+                converter,
+                options);
+
+            return jsonPropertyInfo;
+        }
+
+        /// <summary>
+        /// Create a <see cref="JsonPropertyInfo"/> for a given Type.
+        /// See <seealso cref="JsonClassInfo.PropertyInfoForClassInfo"/>.
+        /// </summary>
+        internal static JsonPropertyInfo CreatePropertyInfoForClassInfo(
+            Type declaredPropertyType,
+            Type runtimePropertyType,
+            JsonConverter converter,
+            JsonSerializerOptions options)
+        {
+            return CreateProperty(
+                declaredPropertyType: declaredPropertyType,
+                runtimePropertyType: runtimePropertyType,
+                propertyInfo: null, // Not a real property so this is null.
+                parentClassType: typeof(object), // a dummy value (not used)
+                converter : converter,
+                options);
+        }
+
+        // AggressiveInlining used although a large method it is only called from one location and is on a hot path.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public JsonPropertyInfo GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
+        {
+            JsonPropertyInfo? info = null;
+
+            // Keep a local copy of the cache in case it changes by another thread.
+            PropertyRef[]? localPropertyRefsSorted = _propertyRefsSorted;
+
+            ulong key = GetKey(propertyName);
+
+            // If there is an existing cache, then use it.
+            if (localPropertyRefsSorted != null)
+            {
+                // Start with the current property index, and then go forwards\backwards.
+                int propertyIndex = frame.PropertyIndex;
+
+                int count = localPropertyRefsSorted.Length;
+                int iForward = Math.Min(propertyIndex, count);
+                int iBackward = iForward - 1;
+
+                while (true)
+                {
+                    if (iForward < count)
+                    {
+                        PropertyRef propertyRef = localPropertyRefsSorted[iForward];
+                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
+                        {
+                            return info;
+                        }
+
+                        ++iForward;
+
+                        if (iBackward >= 0)
+                        {
+                            propertyRef = localPropertyRefsSorted[iBackward];
+                            if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
+                            {
+                                return info;
+                            }
+
+                            --iBackward;
+                        }
+                    }
+                    else if (iBackward >= 0)
+                    {
+                        PropertyRef propertyRef = localPropertyRefsSorted[iBackward];
+                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
+                        {
+                            return info;
+                        }
+
+                        --iBackward;
+                    }
+                    else
+                    {
+                        // Property was not found.
+                        break;
+                    }
+                }
+            }
+
+            // No cached item was found. Try the main list which has all of the properties.
+
+            string stringPropertyName = JsonHelpers.Utf8GetString(propertyName);
+
+            Debug.Assert(PropertyCache != null);
+
+            if (!PropertyCache.TryGetValue(stringPropertyName, out info))
+            {
+                info = JsonPropertyInfo.s_missingProperty;
+            }
+
+            Debug.Assert(info != null);
+
+            // Three code paths to get here:
+            // 1) info == s_missingProperty. Property not found.
+            // 2) key == info.PropertyNameKey. Exact match found.
+            // 3) key != info.PropertyNameKey. Match found due to case insensitivity.
+            Debug.Assert(info == JsonPropertyInfo.s_missingProperty || key == info.PropertyNameKey || Options.PropertyNameCaseInsensitive);
+
+            // Check if we should add this to the cache.
+            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
+            int cacheCount = 0;
+            if (localPropertyRefsSorted != null)
+            {
+                cacheCount = localPropertyRefsSorted.Length;
+            }
+
+            // Do a quick check for the stable (after warm-up) case.
+            if (cacheCount < PropertyNameCountCacheThreshold)
+            {
+                // Do a slower check for the warm-up case.
+                if (frame.PropertyRefCache != null)
+                {
+                    cacheCount += frame.PropertyRefCache.Count;
+                }
+
+                // Check again to append the cache up to the threshold.
+                if (cacheCount < PropertyNameCountCacheThreshold)
+                {
+                    if (frame.PropertyRefCache == null)
+                    {
+                        frame.PropertyRefCache = new List<PropertyRef>();
+                    }
+
+                    PropertyRef propertyRef = new PropertyRef(key, info);
+                    frame.PropertyRefCache.Add(propertyRef);
+                }
+            }
+
+            return info;
+        }
+
+        // AggressiveInlining used although a large method it is only called from one location and is on a hot path.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetParameter(
+            ReadOnlySpan<byte> propertyName,
+            ref ReadStackFrame frame,
+            out JsonParameterInfo? jsonParameterInfo)
+        {
+            JsonParameterInfo? info = null;
+
+            // Keep a local copy of the cache in case it changes by another thread.
+            ParameterRef[]? localParameterRefsSorted = _parameterRefsSorted;
+
+            ulong key = GetKey(propertyName);
+
+            // If there is an existing cache, then use it.
+            if (localParameterRefsSorted != null)
+            {
+                // Start with the current parameter index, and then go forwards\backwards.
+                int parameterIndex = frame.CtorArgumentState!.ParameterIndex;
+
+                int count = localParameterRefsSorted.Length;
+                int iForward = Math.Min(parameterIndex, count);
+                int iBackward = iForward - 1;
+
+                while (true)
+                {
+                    if (iForward < count)
+                    {
+                        ParameterRef parameterRef = localParameterRefsSorted[iForward];
+                        if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
+                        {
+                            jsonParameterInfo = info;
+                            return true;
+                        }
+
+                        ++iForward;
+
+                        if (iBackward >= 0)
+                        {
+                            parameterRef = localParameterRefsSorted[iBackward];
+                            if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
+                            {
+                                jsonParameterInfo = info;
+                                return true;
+                            }
+
+                            --iBackward;
+                        }
+                    }
+                    else if (iBackward >= 0)
+                    {
+                        ParameterRef parameterRef = localParameterRefsSorted[iBackward];
+                        if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
+                        {
+                            jsonParameterInfo = info;
+                            return true;
+                        }
+
+                        --iBackward;
+                    }
+                    else
+                    {
+                        // Property was not found.
+                        break;
+                    }
+                }
+            }
+
+            string propertyNameAsString = JsonHelpers.Utf8GetString(propertyName);
+
+            Debug.Assert(ParameterCache != null);
+
+            if (!ParameterCache.TryGetValue(propertyNameAsString, out info))
+            {
+                // Constructor parameter not found. We'll check if it's a property next.
+                jsonParameterInfo = null;
+                return false;
+            }
+
+            jsonParameterInfo = info;
+            Debug.Assert(info != null);
+
+            // Two code paths to get here:
+            // 1) key == info.PropertyNameKey. Exact match found.
+            // 2) key != info.PropertyNameKey. Match found due to case insensitivity.
+            // TODO: recheck these conditions
+            Debug.Assert(key == info.ParameterNameKey ||
+                propertyNameAsString.Equals(info.NameAsString, StringComparison.OrdinalIgnoreCase));
+
+            // Check if we should add this to the cache.
+            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
+            int cacheCount = 0;
+            if (localParameterRefsSorted != null)
+            {
+                cacheCount = localParameterRefsSorted.Length;
+            }
+
+            // Do a quick check for the stable (after warm-up) case.
+            if (cacheCount < ParameterNameCountCacheThreshold)
+            {
+                // Do a slower check for the warm-up case.
+                if (frame.CtorArgumentState!.ParameterRefCache != null)
+                {
+                    cacheCount += frame.CtorArgumentState.ParameterRefCache.Count;
+                }
+
+                // Check again to append the cache up to the threshold.
+                if (cacheCount < ParameterNameCountCacheThreshold)
+                {
+                    if (frame.CtorArgumentState.ParameterRefCache == null)
+                    {
+                        frame.CtorArgumentState.ParameterRefCache = new List<ParameterRef>();
+                    }
+
+                    ParameterRef parameterRef = new ParameterRef(key, jsonParameterInfo);
+                    frame.CtorArgumentState.ParameterRefCache.Add(parameterRef);
+                }
+            }
+
+            return true;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, [NotNullWhen(true)] ref JsonPropertyInfo? info)
+        {
+            if (key == propertyRef.Key)
+            {
+                // We compare the whole name, although we could skip the first 7 bytes (but it's not any faster)
+                if (propertyName.Length <= PropertyNameKeyLength ||
+                    propertyName.SequenceEqual(propertyRef.Info.Name))
+                {
+                    info = propertyRef.Info;
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool TryIsParameterRefEqual(in ParameterRef parameterRef, ReadOnlySpan<byte> parameterName, ulong key, [NotNullWhen(true)] ref JsonParameterInfo? info)
+        {
+            if (key == parameterRef.Key)
+            {
+                // We compare the whole name, although we could skip the first 7 bytes (but it's not any faster)
+                if (parameterName.Length <= PropertyNameKeyLength ||
+                    parameterName.SequenceEqual(parameterRef.Info.ParameterName))
+                {
+                    info = parameterRef.Info;
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Get a key from the property name.
+        /// The key consists of the first 7 bytes of the property name and then the length.
+        /// </summary>
+        // AggressiveInlining used since this method is only called from two locations and is on a hot path.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ulong GetKey(ReadOnlySpan<byte> propertyName)
+        {
+            const int BitsInByte = 8;
+            ulong key;
+            int length = propertyName.Length;
+
+            if (length > 7)
+            {
+                key = MemoryMarshal.Read<ulong>(propertyName);
+
+                // Max out the length byte.
+                // This will cause the comparison logic to always test for equality against the full contents
+                // when the first 7 bytes are the same.
+                key |= 0xFF00000000000000;
+
+                // It is also possible to include the length up to 0xFF in order to prevent false positives
+                // when the first 7 bytes match but a different length (up to 0xFF). However the extra logic
+                // slows key generation in the majority of cases:
+                // key &= 0x00FFFFFFFFFFFFFF;
+                // key |= (ulong) 7 << Math.Max(length, 0xFF);
+            }
+            else if (length > 3)
+            {
+                key = MemoryMarshal.Read<uint>(propertyName);
+
+                if (length == 7)
+                {
+                    key |= (ulong)propertyName[6] << (6 * BitsInByte)
+                        | (ulong)propertyName[5] << (5 * BitsInByte)
+                        | (ulong)propertyName[4] << (4 * BitsInByte)
+                        | (ulong)7 << (7 * BitsInByte);
+                }
+                else if (length == 6)
+                {
+                    key |= (ulong)propertyName[5] << (5 * BitsInByte)
+                        | (ulong)propertyName[4] << (4 * BitsInByte)
+                        | (ulong)6 << (7 * BitsInByte);
+                }
+                else if (length == 5)
+                {
+                    key |= (ulong)propertyName[4] << (4 * BitsInByte)
+                        | (ulong)5 << (7 * BitsInByte);
+                }
+                else
+                {
+                    key |= (ulong)4 << (7 * BitsInByte);
+                }
+            }
+            else if (length > 1)
+            {
+                key = MemoryMarshal.Read<ushort>(propertyName);
+
+                if (length == 3)
+                {
+                    key |= (ulong)propertyName[2] << (2 * BitsInByte)
+                        | (ulong)3 << (7 * BitsInByte);
+                }
+                else
+                {
+                    key |= (ulong)2 << (7 * BitsInByte);
+                }
+            }
+            else if (length == 1)
+            {
+                key = propertyName[0]
+                    | (ulong)1 << (7 * BitsInByte);
+            }
+            else
+            {
+                // An empty name is valid.
+                key = 0;
+            }
+
+            // Verify key contains the embedded bytes as expected.
+            Debug.Assert(
+                (length < 1 || propertyName[0] == ((key & ((ulong)0xFF << 8 * 0)) >> 8 * 0)) &&
+                (length < 2 || propertyName[1] == ((key & ((ulong)0xFF << 8 * 1)) >> 8 * 1)) &&
+                (length < 3 || propertyName[2] == ((key & ((ulong)0xFF << 8 * 2)) >> 8 * 2)) &&
+                (length < 4 || propertyName[3] == ((key & ((ulong)0xFF << 8 * 3)) >> 8 * 3)) &&
+                (length < 5 || propertyName[4] == ((key & ((ulong)0xFF << 8 * 4)) >> 8 * 4)) &&
+                (length < 6 || propertyName[5] == ((key & ((ulong)0xFF << 8 * 5)) >> 8 * 5)) &&
+                (length < 7 || propertyName[6] == ((key & ((ulong)0xFF << 8 * 6)) >> 8 * 6)));
+
+            return key;
+        }
+
+        public void UpdateSortedPropertyCache(ref ReadStackFrame frame)
+        {
+            Debug.Assert(frame.PropertyRefCache != null);
+
+            // frame.PropertyRefCache is only read\written by a single thread -- the thread performing
+            // the deserialization for a given object instance.
+
+            List<PropertyRef> listToAppend = frame.PropertyRefCache;
+
+            // _propertyRefsSorted can be accessed by multiple threads, so replace the reference when
+            // appending to it. No lock() is necessary.
+
+            if (_propertyRefsSorted != null)
+            {
+                List<PropertyRef> replacementList = new List<PropertyRef>(_propertyRefsSorted);
+                Debug.Assert(replacementList.Count <= PropertyNameCountCacheThreshold);
+
+                // Verify replacementList will not become too large.
+                while (replacementList.Count + listToAppend.Count > PropertyNameCountCacheThreshold)
+                {
+                    // This code path is rare; keep it simple by using RemoveAt() instead of RemoveRange() which requires calculating index\count.
+                    listToAppend.RemoveAt(listToAppend.Count - 1);
+                }
+
+                // Add the new items; duplicates are possible but that is tolerated during property lookup.
+                replacementList.AddRange(listToAppend);
+                _propertyRefsSorted = replacementList.ToArray();
+            }
+            else
+            {
+                _propertyRefsSorted = listToAppend.ToArray();
+            }
+
+            frame.PropertyRefCache = null;
+        }
+
+        public void UpdateSortedParameterCache(ref ReadStackFrame frame)
+        {
+            Debug.Assert(frame.CtorArgumentState!.ParameterRefCache != null);
+
+            // frame.PropertyRefCache is only read\written by a single thread -- the thread performing
+            // the deserialization for a given object instance.
+
+            List<ParameterRef> listToAppend = frame.CtorArgumentState.ParameterRefCache;
+
+            // _parameterRefsSorted can be accessed by multiple threads, so replace the reference when
+            // appending to it. No lock() is necessary.
+
+            if (_parameterRefsSorted != null)
+            {
+                List<ParameterRef> replacementList = new List<ParameterRef>(_parameterRefsSorted);
+                Debug.Assert(replacementList.Count <= ParameterNameCountCacheThreshold);
+
+                // Verify replacementList will not become too large.
+                while (replacementList.Count + listToAppend.Count > ParameterNameCountCacheThreshold)
+                {
+                    // This code path is rare; keep it simple by using RemoveAt() instead of RemoveRange() which requires calculating index\count.
+                    listToAppend.RemoveAt(listToAppend.Count - 1);
+                }
+
+                // Add the new items; duplicates are possible but that is tolerated during property lookup.
+                replacementList.AddRange(listToAppend);
+                _parameterRefsSorted = replacementList.ToArray();
+            }
+            else
+            {
+                _parameterRefsSorted = listToAppend.ToArray();
+            }
+
+            frame.CtorArgumentState.ParameterRefCache = null;
+        }
+    }
+}
index ab59358..8f35740 100644 (file)
@@ -4,10 +4,7 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Text.Json.Serialization;
 
 namespace System.Text.Json
@@ -15,26 +12,12 @@ namespace System.Text.Json
     [DebuggerDisplay("ClassType.{ClassType}, {Type.Name}")]
     internal sealed partial class JsonClassInfo
     {
-        // The length of the property name embedded in the key (in bytes).
-        // The key is a ulong (8 bytes) containing the first 7 bytes of the property name
-        // followed by a byte representing the length.
-        private const int PropertyNameKeyLength = 7;
-
-        // The limit to how many property names from the JSON are cached in _propertyRefsSorted before using PropertyCache.
-        private const int PropertyNameCountCacheThreshold = 64;
-
-        // All of the serializable properties on a POCO (except the optional extension property) keyed on property name.
-        public volatile Dictionary<string, JsonPropertyInfo>? PropertyCache;
+        public delegate object? ConstructorDelegate();
 
-        // All of the serializable properties on a POCO including the optional extension property.
-        // Used for performance during serialization instead of 'PropertyCache' above.
-        public volatile JsonPropertyInfo[]? PropertyCacheArray;
+        public delegate T ParameterizedConstructorDelegate<T>(object[] arguments);
 
-        // Fast cache of properties by first JSON ordering; may not contain all properties. Accessed before PropertyCache.
-        // Use an array (instead of List<T>) for highest performance.
-        private volatile PropertyRef[]? _propertyRefsSorted;
+        public delegate T ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>(TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3);
 
-        public delegate object? ConstructorDelegate();
         public ConstructorDelegate? CreateObject { get; private set; }
 
         public ClassType ClassType { get; private set; }
@@ -73,41 +56,23 @@ namespace System.Text.Json
 
         public Type Type { get; private set; }
 
-        public void UpdateSortedPropertyCache(ref ReadStackFrame frame)
-        {
-            Debug.Assert(frame.PropertyRefCache != null);
-
-            // frame.PropertyRefCache is only read\written by a single thread -- the thread performing
-            // the deserialization for a given object instance.
-
-            List<PropertyRef> listToAppend = frame.PropertyRefCache;
-
-            // _propertyRefsSorted can be accessed by multiple threads, so replace the reference when
-            // appending to it. No lock() is necessary.
-
-            if (_propertyRefsSorted != null)
-            {
-                List<PropertyRef> replacementList = new List<PropertyRef>(_propertyRefsSorted);
-                Debug.Assert(replacementList.Count <= PropertyNameCountCacheThreshold);
-
-                // Verify replacementList will not become too large.
-                while (replacementList.Count + listToAppend.Count > PropertyNameCountCacheThreshold)
-                {
-                    // This code path is rare; keep it simple by using RemoveAt() instead of RemoveRange() which requires calculating index\count.
-                    listToAppend.RemoveAt(listToAppend.Count - 1);
-                }
-
-                // Add the new items; duplicates are possible but that is tolerated during property lookup.
-                replacementList.AddRange(listToAppend);
-                _propertyRefsSorted = replacementList.ToArray();
-            }
-            else
-            {
-                _propertyRefsSorted = listToAppend.ToArray();
-            }
-
-            frame.PropertyRefCache = null;
-        }
+        /// <summary>
+        /// The JsonPropertyInfo for this JsonClassInfo. It is used to obtain the converter for the ClassInfo.
+        /// </summary>
+        /// <remarks>
+        /// The returned JsonPropertyInfo does not represent a real property; instead it represents either:
+        /// a collection element type,
+        /// a generic type parameter,
+        /// a property type (if pushed to a new stack frame),
+        /// or the root type passed into the root serialization APIs.
+        /// For example, for a property returning <see cref="Collections.Generic.List{T}"/> where T is a string,
+        /// a JsonClassInfo will be created with .Type=typeof(string) and .PropertyInfoForClassInfo=JsonPropertyInfo{string}.
+        /// Without this property, a "Converter" property would need to be added to JsonClassInfo and there would be several more
+        /// `if` statements to obtain the converter from either the actual JsonPropertyInfo (for a real property) or from the
+        /// ClassInfo (for the cases mentioned above). In addition, methods that have a JsonPropertyInfo argument would also likely
+        /// need to add an argument for JsonClassInfo.
+        /// </remarks>
+        public JsonPropertyInfo PropertyInfoForClassInfo { get; private set; }
 
         public JsonClassInfo(Type type, JsonSerializerOptions options)
         {
@@ -128,6 +93,9 @@ namespace System.Text.Json
             {
                 case ClassType.Object:
                     {
+                        // Create the policy property.
+                        PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(type, runtimeType, converter!, options);
+
                         CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
 
                         PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
@@ -161,7 +129,7 @@ namespace System.Text.Json
                                     }
                                     else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)
                                     {
-                                        ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
+                                        ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
                                     }
                                     // else ignore jsonPropertyInfo since it has [JsonIgnore].
                                 }
@@ -188,6 +156,12 @@ namespace System.Text.Json
                         PropertyCache = cache;
                         cache.Values.CopyTo(cacheArray, 0);
                         PropertyCacheArray = cacheArray;
+
+                        if (converter.ConstructorIsParameterized)
+                        {
+                            converter.CreateConstructorDelegate(options);
+                            InitializeConstructorParameters(converter.ConstructorInfo);
+                        }
                     }
                     break;
                 case ClassType.Enumerable:
@@ -214,308 +188,141 @@ namespace System.Text.Json
             }
         }
 
-        private bool DetermineExtensionDataProperty(Dictionary<string, JsonPropertyInfo> cache)
+        private void InitializeConstructorParameters(ConstructorInfo constructorInfo)
         {
-            JsonPropertyInfo? jsonPropertyInfo = GetPropertyWithUniqueAttribute(typeof(JsonExtensionDataAttribute), cache);
-            if (jsonPropertyInfo != null)
-            {
-                Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType;
-                if (typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
-                    typeof(IDictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType))
-                {
-                    JsonConverter converter = Options.GetConverter(declaredPropertyType);
-                    Debug.Assert(converter != null);
-                }
-                else
-                {
-                    ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(this, jsonPropertyInfo);
-                }
-
-                DataExtensionProperty = jsonPropertyInfo;
+            ParameterInfo[] parameters = constructorInfo!.GetParameters();
+            Dictionary<string, JsonParameterInfo> parameterCache = CreateParameterCache(parameters.Length, Options);
 
-                return true;
-            }
+            Dictionary<string, JsonPropertyInfo> propertyCache = PropertyCache!;
 
-            return false;
-        }
-
-        private JsonPropertyInfo? GetPropertyWithUniqueAttribute(Type attributeType, Dictionary<string, JsonPropertyInfo> cache)
-        {
-            JsonPropertyInfo? property = null;
-
-            foreach (JsonPropertyInfo jsonPropertyInfo in cache.Values)
+            foreach (ParameterInfo parameterInfo in parameters)
             {
-                Debug.Assert(jsonPropertyInfo.PropertyInfo != null);
-                Attribute? attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType);
-                if (attribute != null)
-                {
-                    if (property != null)
-                    {
-                        ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, attributeType);
-                    }
-
-                    property = jsonPropertyInfo;
-                }
-            }
-
-            return property;
-        }
-
-        // AggressiveInlining used although a large method it is only called from one location and is on a hot path.
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public JsonPropertyInfo GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
-        {
-            JsonPropertyInfo? info = null;
-
-            // Keep a local copy of the cache in case it changes by another thread.
-            PropertyRef[]? localPropertyRefsSorted = _propertyRefsSorted;
+                PropertyInfo? firstMatch = null;
+                bool isBound = false;
 
-            ulong key = GetKey(propertyName);
-
-            // If there is an existing cache, then use it.
-            if (localPropertyRefsSorted != null)
-            {
-                // Start with the current property index, and then go forwards\backwards.
-                int propertyIndex = frame.PropertyIndex;
+                foreach (JsonPropertyInfo jsonPropertyInfo in PropertyCacheArray!)
+                {
+                    // This is not null because it is an actual
+                    // property on a type, not a "policy property".
+                    PropertyInfo propertyInfo = jsonPropertyInfo.PropertyInfo!;
 
-                int count = localPropertyRefsSorted.Length;
-                int iForward = Math.Min(propertyIndex, count);
-                int iBackward = iForward - 1;
+                    string camelCasePropName = JsonNamingPolicy.CamelCase.ConvertName(propertyInfo.Name);
 
-                while (true)
-                {
-                    if (iForward < count)
+                    if (parameterInfo.Name == camelCasePropName &&
+                        parameterInfo.ParameterType == propertyInfo.PropertyType)
                     {
-                        PropertyRef propertyRef = localPropertyRefsSorted[iForward];
-                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
+                        if (isBound)
                         {
-                            return info;
+                            Debug.Assert(firstMatch != null);
+
+                            // Multiple object properties cannot bind to the same
+                            // constructor parameter.
+                            ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters(
+                                Type,
+                                parameterInfo,
+                                firstMatch,
+                                propertyInfo,
+                                constructorInfo);
                         }
 
-                        ++iForward;
+                        JsonParameterInfo jsonParameterInfo = AddConstructorParameter(parameterInfo, jsonPropertyInfo, Options);
 
-                        if (iBackward >= 0)
-                        {
-                            propertyRef = localPropertyRefsSorted[iBackward];
-                            if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
-                            {
-                                return info;
-                            }
+                        // One object property cannot map to multiple constructor
+                        // parameters (ConvertName above can't return multiple strings).
+                        parameterCache.Add(jsonParameterInfo.NameAsString, jsonParameterInfo);
 
-                            --iBackward;
-                        }
-                    }
-                    else if (iBackward >= 0)
-                    {
-                        PropertyRef propertyRef = localPropertyRefsSorted[iBackward];
-                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
-                        {
-                            return info;
-                        }
+                        // Remove property from deserialization cache to reduce the number of JsonPropertyInfos considered during JSON matching.
+                        propertyCache.Remove(jsonPropertyInfo.NameAsString!);
 
-                        --iBackward;
-                    }
-                    else
-                    {
-                        // Property was not found.
-                        break;
+                        isBound = true;
+                        firstMatch = propertyInfo;
                     }
                 }
             }
 
-            // No cached item was found. Try the main list which has all of the properties.
-
-            string stringPropertyName = JsonHelpers.Utf8GetString(propertyName);
-
-            Debug.Assert(PropertyCache != null);
-
-            if (!PropertyCache.TryGetValue(stringPropertyName, out info))
+            // It is invalid for the extension data property to bind with a constructor argument.
+            if (DataExtensionProperty != null &&
+                parameterCache.ContainsKey(DataExtensionProperty.NameAsString!))
             {
-                info = JsonPropertyInfo.s_missingProperty;
+                ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.PropertyInfo!, Type, constructorInfo);
             }
 
-            Debug.Assert(info != null);
-
-            // Three code paths to get here:
-            // 1) info == s_missingProperty. Property not found.
-            // 2) key == info.PropertyNameKey. Exact match found.
-            // 3) key != info.PropertyNameKey. Match found due to case insensitivity.
-            Debug.Assert(info == JsonPropertyInfo.s_missingProperty || key == info.PropertyNameKey || Options.PropertyNameCaseInsensitive);
+            ParameterCache = parameterCache;
+            ParameterCount = parameters.Length;
 
-            // Check if we should add this to the cache.
-            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
-            int cacheCount = 0;
-            if (localPropertyRefsSorted != null)
-            {
-                cacheCount = localPropertyRefsSorted.Length;
-            }
+            PropertyCache = propertyCache;
+        }
 
-            // Do a quick check for the stable (after warm-up) case.
-            if (cacheCount < PropertyNameCountCacheThreshold)
+        public bool DetermineExtensionDataProperty(Dictionary<string, JsonPropertyInfo> cache)
+        {
+            JsonPropertyInfo? jsonPropertyInfo = GetPropertyWithUniqueAttribute(Type, typeof(JsonExtensionDataAttribute), cache);
+            if (jsonPropertyInfo != null)
             {
-                // Do a slower check for the warm-up case.
-                if (frame.PropertyRefCache != null)
+                Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType;
+                if (typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
+                    typeof(IDictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType))
                 {
-                    cacheCount += frame.PropertyRefCache.Count;
+                    JsonConverter converter = Options.GetConverter(declaredPropertyType);
+                    Debug.Assert(converter != null);
                 }
-
-                // Check again to append the cache up to the threshold.
-                if (cacheCount < PropertyNameCountCacheThreshold)
+                else
                 {
-                    if (frame.PropertyRefCache == null)
-                    {
-                        frame.PropertyRefCache = new List<PropertyRef>();
-                    }
-
-                    PropertyRef propertyRef = new PropertyRef(key, info);
-                    frame.PropertyRefCache.Add(propertyRef);
+                    ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, jsonPropertyInfo);
                 }
-            }
-
-            return info;
-        }
 
-        private Dictionary<string, JsonPropertyInfo> CreatePropertyCache(int capacity)
-        {
-            StringComparer comparer;
-
-            if (Options.PropertyNameCaseInsensitive)
-            {
-                comparer = StringComparer.OrdinalIgnoreCase;
-            }
-            else
-            {
-                comparer = StringComparer.Ordinal;
+                DataExtensionProperty = jsonPropertyInfo;
+                return true;
             }
 
-            return new Dictionary<string, JsonPropertyInfo>(capacity, comparer);
+            return false;
         }
 
-        /// <summary>
-        /// The JsonPropertyInfo for this JsonClassInfo. It is used to obtain the converter for the ClassInfo.
-        /// </summary>
-        /// <remarks>
-        /// The returned JsonPropertyInfo does not represent a real property; instead it represents either:
-        /// a collection element type,
-        /// a generic type parameter,
-        /// a property type (if pushed to a new stack frame),
-        /// or the root type passed into the root serialization APIs.
-        /// For example, for a property returning <see cref="Collections.Generic.List{T}"/> where T is a string,
-        /// a JsonClassInfo will be created with .Type=typeof(string) and .PropertyInfoForClassInfo=JsonPropertyInfo{string}.
-        /// Without this property, a "Converter" property would need to be added to JsonClassInfo and there would be several more
-        /// `if` statements to obtain the converter from either the actual JsonPropertyInfo (for a real property) or from the
-        /// ClassInfo (for the cases mentioned above). In addition, methods that have a JsonPropertyInfo argument would also likely
-        /// need to add an argument for JsonClassInfo.
-        /// </remarks>
-        public JsonPropertyInfo PropertyInfoForClassInfo { get; private set; }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, [NotNullWhen(true)] ref JsonPropertyInfo? info)
+        private static JsonPropertyInfo? GetPropertyWithUniqueAttribute(Type classType, Type attributeType, Dictionary<string, JsonPropertyInfo> cache)
         {
-            if (key == propertyRef.Key)
+            JsonPropertyInfo? property = null;
+
+            foreach (JsonPropertyInfo jsonPropertyInfo in cache.Values)
             {
-                // We compare the whole name, although we could skip the first 7 bytes (but it's not any faster)
-                if (propertyName.Length <= PropertyNameKeyLength ||
-                    propertyName.SequenceEqual(propertyRef.Info.Name))
+                Debug.Assert(jsonPropertyInfo.PropertyInfo != null);
+                Attribute? attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType);
+                if (attribute != null)
                 {
-                    info = propertyRef.Info;
-                    return true;
+                    if (property != null)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(classType, attributeType);
+                    }
+
+                    property = jsonPropertyInfo;
                 }
             }
 
-            return false;
+            return property;
         }
 
-        /// <summary>
-        /// Get a key from the property name.
-        /// The key consists of the first 7 bytes of the property name and then the length.
-        /// </summary>
-        // AggressiveInlining used since this method is only called from two locations and is on a hot path.
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static ulong GetKey(ReadOnlySpan<byte> propertyName)
+        private static JsonParameterInfo AddConstructorParameter(
+            ParameterInfo parameterInfo,
+            JsonPropertyInfo jsonPropertyInfo,
+            JsonSerializerOptions options)
         {
-            const int BitsInByte = 8;
-            ulong key;
-            int length = propertyName.Length;
+            string matchingPropertyName = jsonPropertyInfo.NameAsString!;
 
-            if (length > 7)
+            if (jsonPropertyInfo.IsIgnored)
             {
-                key = MemoryMarshal.Read<ulong>(propertyName);
-
-                // Max out the length byte.
-                // This will cause the comparison logic to always test for equality against the full contents
-                // when the first 7 bytes are the same.
-                key |= 0xFF00000000000000;
-
-                // It is also possible to include the length up to 0xFF in order to prevent false positives
-                // when the first 7 bytes match but a different length (up to 0xFF). However the extra logic
-                // slows key generation in the majority of cases:
-                // key &= 0x00FFFFFFFFFFFFFF;
-                // key |= (ulong) 7 << Math.Max(length, 0xFF);
+                return JsonParameterInfo.CreateIgnoredParameterPlaceholder(matchingPropertyName, parameterInfo, options);
             }
-            else if (length > 3)
-            {
-                key = MemoryMarshal.Read<uint>(propertyName);
 
-                if (length == 7)
-                {
-                    key |= (ulong)propertyName[6] << (6 * BitsInByte)
-                        | (ulong)propertyName[5] << (5 * BitsInByte)
-                        | (ulong)propertyName[4] << (4 * BitsInByte)
-                        | (ulong)7 << (7 * BitsInByte);
-                }
-                else if (length == 6)
-                {
-                    key |= (ulong)propertyName[5] << (5 * BitsInByte)
-                        | (ulong)propertyName[4] << (4 * BitsInByte)
-                        | (ulong)6 << (7 * BitsInByte);
-                }
-                else if (length == 5)
-                {
-                    key |= (ulong)propertyName[4] << (4 * BitsInByte)
-                        | (ulong)5 << (7 * BitsInByte);
-                }
-                else
-                {
-                    key |= (ulong)4 << (7 * BitsInByte);
-                }
-            }
-            else if (length > 1)
-            {
-                key = MemoryMarshal.Read<ushort>(propertyName);
+            JsonConverter converter = jsonPropertyInfo.ConverterBase;
 
-                if (length == 3)
-                {
-                    key |= (ulong)propertyName[2] << (2 * BitsInByte)
-                        | (ulong)3 << (7 * BitsInByte);
-                }
-                else
-                {
-                    key |= (ulong)2 << (7 * BitsInByte);
-                }
-            }
-            else if (length == 1)
-            {
-                key = propertyName[0]
-                    | (ulong)1 << (7 * BitsInByte);
-            }
-            else
-            {
-                // An empty name is valid.
-                key = 0;
-            }
+            JsonParameterInfo jsonParameterInfo = converter.CreateJsonParameterInfo();
+            jsonParameterInfo.Initialize(
+                matchingPropertyName,
+                jsonPropertyInfo.DeclaredPropertyType,
+                jsonPropertyInfo.RuntimePropertyType!,
+                parameterInfo,
+                converter,
+                options);
 
-            // Verify key contains the embedded bytes as expected.
-            Debug.Assert(
-                (length < 1 || propertyName[0] == ((key & ((ulong)0xFF << 8 * 0)) >> 8 * 0)) &&
-                (length < 2 || propertyName[1] == ((key & ((ulong)0xFF << 8 * 1)) >> 8 * 1)) &&
-                (length < 3 || propertyName[2] == ((key & ((ulong)0xFF << 8 * 2)) >> 8 * 2)) &&
-                (length < 4 || propertyName[3] == ((key & ((ulong)0xFF << 8 * 3)) >> 8 * 3)) &&
-                (length < 5 || propertyName[4] == ((key & ((ulong)0xFF << 8 * 4)) >> 8 * 4)) &&
-                (length < 6 || propertyName[5] == ((key & ((ulong)0xFF << 8 * 5)) >> 8 * 5)) &&
-                (length < 7 || propertyName[6] == ((key & ((ulong)0xFF << 8 * 6)) >> 8 * 6)));
-
-            return key;
+            return jsonParameterInfo;
         }
 
         // This method gets the runtime information for a given type or property.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConstructorAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConstructorAttribute.cs
new file mode 100644 (file)
index 0000000..eb02466
--- /dev/null
@@ -0,0 +1,19 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// When placed on a constructor, indicates that the constructor should be used to create
+    /// instances of the type on deserialization.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)]
+    public sealed class JsonConstructorAttribute : JsonAttribute
+    {
+        /// <summary>
+        /// Initializes a new instance of <see cref="JsonConstructorAttribute"/>.
+        /// </summary>
+        public JsonConstructorAttribute() { }
+    }
+}
index 8d7b4bb..be1cbd5 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Reflection;
+
 namespace System.Text.Json.Serialization
 {
     /// <summary>
@@ -34,6 +36,8 @@ namespace System.Text.Json.Serialization
 
         internal abstract JsonPropertyInfo CreateJsonPropertyInfo();
 
+        internal abstract JsonParameterInfo CreateJsonParameterInfo();
+
         internal abstract Type? ElementType { get; }
 
         /// <summary>
@@ -70,5 +74,12 @@ namespace System.Text.Json.Serialization
         /// Loosely-typed WriteCore() that forwards to strongly-typed WriteCore().
         /// </summary>
         internal abstract bool WriteCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state);
+
+        // Whether a type (ClassType.Object) is deserialized using a parameterized constructor.
+        internal virtual bool ConstructorIsParameterized => false;
+
+        internal ConstructorInfo ConstructorInfo { get; set; } = null!;
+
+        internal virtual void CreateConstructorDelegate(JsonSerializerOptions options) { }
     }
 }
index 63faf51..caea89a 100644 (file)
@@ -46,6 +46,11 @@ namespace System.Text.Json.Serialization
             throw new InvalidOperationException();
         }
 
+        internal override JsonParameterInfo CreateJsonParameterInfo()
+        {
+            throw new InvalidOperationException();
+        }
+
         internal sealed override Type? ElementType => null;
 
         internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
index 450b871..c7fc179 100644 (file)
@@ -47,6 +47,11 @@ namespace System.Text.Json.Serialization
             return new JsonPropertyInfo<T>();
         }
 
+        internal override sealed JsonParameterInfo CreateJsonParameterInfo()
+        {
+            return new JsonParameterInfo<T>();
+        }
+
         internal override Type? ElementType => null;
 
         // Allow a converter that can't be null to return a null value representation, such as JsonElement or Nullable<>.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfo.cs
new file mode 100644 (file)
index 0000000..7f50762
--- /dev/null
@@ -0,0 +1,108 @@
+// 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.Diagnostics;
+using System.Reflection;
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Holds relevant state about a method parameter, like the default value of
+    /// the parameter, and the position in the method's parameter list.
+    /// </summary>
+    [DebuggerDisplay("ParameterInfo={ParameterInfo}")]
+    internal abstract class JsonParameterInfo
+    {
+        private Type _runtimePropertyType = null!;
+
+        public abstract JsonConverter ConverterBase { get; }
+
+        // The default value of the parameter. This is `DefaultValue` of the `ParameterInfo`, if specified, or the CLR `default` for the `ParameterType`.
+        public object? DefaultValue { get; protected set; }
+
+        // The name from a Json value. This is cached for performance on first deserialize.
+        public byte[]? JsonPropertyName { get; set; }
+
+        // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions.
+        protected JsonSerializerOptions Options { get; set; } = null!; // initialized in Init method
+
+        public ParameterInfo ParameterInfo { get; private set; } = null!;
+
+        // The name of the parameter as UTF-8 bytes.
+        public byte[] ParameterName { get; private set; } = null!;
+
+        // The name of the parameter.
+        public string NameAsString { get; private set; } = null!;
+
+        // Key for fast property name lookup.
+        public ulong ParameterNameKey { get; private set; }
+
+        // The zero-based position of the parameter in the formal parameter list.
+        public int Position { get; private set; }
+
+        private JsonClassInfo? _runtimeClassInfo;
+        public JsonClassInfo RuntimeClassInfo
+        {
+            get
+            {
+                if (_runtimeClassInfo == null)
+                {
+                    _runtimeClassInfo = Options.GetOrAddClass(_runtimePropertyType);
+                }
+
+                return _runtimeClassInfo;
+            }
+        }
+
+        public bool ShouldDeserialize { get; private set; }
+
+        public virtual void Initialize(
+            string matchingPropertyName,
+            Type declaredPropertyType,
+            Type runtimePropertyType,
+            ParameterInfo parameterInfo,
+            JsonConverter converter,
+            JsonSerializerOptions options)
+        {
+            _runtimePropertyType = runtimePropertyType;
+
+            Options = options;
+            ParameterInfo = parameterInfo;
+            Position = parameterInfo.Position;
+            ShouldDeserialize = true;
+
+            DetermineParameterName(matchingPropertyName);
+        }
+
+        private void DetermineParameterName(string matchingPropertyName)
+        {
+            NameAsString = matchingPropertyName;
+
+            // `NameAsString` is valid UTF16, so just call the simple UTF16->UTF8 encoder.
+            ParameterName = Encoding.UTF8.GetBytes(NameAsString);
+
+            ParameterNameKey = JsonClassInfo.GetKey(ParameterName);
+        }
+
+        // Create a parameter that is ignored at run-time. It uses the same type (typeof(sbyte)) to help
+        // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it.
+        public static JsonParameterInfo CreateIgnoredParameterPlaceholder(
+            string matchingPropertyName,
+            ParameterInfo parameterInfo,
+            JsonSerializerOptions options)
+        {
+            JsonParameterInfo jsonParameterInfo = new JsonParameterInfo<sbyte>();
+            jsonParameterInfo.Options = options;
+            jsonParameterInfo.ParameterInfo = parameterInfo;
+            jsonParameterInfo.ShouldDeserialize = false;
+
+            jsonParameterInfo.DetermineParameterName(matchingPropertyName);
+
+            return jsonParameterInfo;
+        }
+
+        public abstract bool ReadJson(ref ReadStack state, ref Utf8JsonReader reader, out object? argument);
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfoOfT.cs
new file mode 100644 (file)
index 0000000..b244477
--- /dev/null
@@ -0,0 +1,112 @@
+// 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.Reflection;
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Represents a strongly-typed parameter to prevent boxing where have less than 4 parameters.
+    /// Holds relevant state like the default value of the parameter, and the position in the method's parameter list.
+    /// </summary>
+    internal class JsonParameterInfo<T> : JsonParameterInfo
+    {
+        private JsonConverter<T> _converter = null!;
+        private Type _runtimePropertyType = null!;
+
+        public override JsonConverter ConverterBase => _converter;
+
+        public T TypedDefaultValue { get; private set; } = default!;
+
+        public override void Initialize(
+            string matchingPropertyName,
+            Type declaredPropertyType,
+            Type runtimePropertyType,
+            ParameterInfo parameterInfo,
+            JsonConverter converter,
+            JsonSerializerOptions options)
+        {
+            base.Initialize(
+                matchingPropertyName,
+                declaredPropertyType,
+                runtimePropertyType,
+                parameterInfo,
+                converter,
+                options);
+
+            _converter = (JsonConverter<T>)converter;
+            _runtimePropertyType = runtimePropertyType;
+
+            if (parameterInfo.HasDefaultValue)
+            {
+                DefaultValue = parameterInfo.DefaultValue;
+                TypedDefaultValue = (T)parameterInfo.DefaultValue!;
+            }
+            else
+            {
+                DefaultValue = TypedDefaultValue;
+            }
+        }
+
+        public override bool ReadJson(ref ReadStack state, ref Utf8JsonReader reader, out object? value)
+        {
+            bool success;
+            bool isNullToken = reader.TokenType == JsonTokenType.Null;
+
+            if (isNullToken &&
+                ((!_converter.HandleNullValue && !state.IsContinuation) || Options.IgnoreNullValues))
+            {
+                // Don't have to check for IgnoreNullValue option here because we set the default value (likely null) regardless
+                value = DefaultValue;
+                return true;
+            }
+            else
+            {
+                // Optimize for internal converters by avoiding the extra call to TryRead.
+                if (_converter.CanUseDirectReadOrWrite)
+                {
+                    value = _converter.Read(ref reader, _runtimePropertyType, Options);
+                    return true;
+                }
+                else
+                {
+                    success = _converter.TryRead(ref reader, _runtimePropertyType, Options, ref state, out T typedValue);
+                    value = typedValue;
+                }
+            }
+
+            return success;
+        }
+
+        public bool ReadJsonTyped(ref ReadStack state, ref Utf8JsonReader reader, out T value)
+        {
+            bool success;
+            bool isNullToken = reader.TokenType == JsonTokenType.Null;
+
+            if (isNullToken &&
+                ((!_converter.HandleNullValue && !state.IsContinuation) || Options.IgnoreNullValues))
+            {
+                // Don't have to check for IgnoreNullValue option here because we set the default value (likely null) regardless
+                value = TypedDefaultValue;
+                return true;
+            }
+            else
+            {
+                // Optimize for internal converters by avoiding the extra call to TryRead.
+                if (_converter.CanUseDirectReadOrWrite)
+                {
+                    value = _converter.Read(ref reader, _runtimePropertyType, Options);
+                    return true;
+                }
+                else
+                {
+                    success = _converter.TryRead(ref reader, _runtimePropertyType, Options, ref state, out value);
+                }
+            }
+
+            return success;
+        }
+    }
+}
index 66fca5d..c519d62 100644 (file)
@@ -4,7 +4,6 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json.Serialization;
 
@@ -38,6 +37,7 @@ namespace System.Text.Json
             jsonPropertyInfo.Options = options;
             jsonPropertyInfo.PropertyInfo = propertyInfo;
             jsonPropertyInfo.DeterminePropertyName();
+            jsonPropertyInfo.IsIgnored = true;
 
             Debug.Assert(!jsonPropertyInfo.ShouldDeserialize);
             Debug.Assert(!jsonPropertyInfo.ShouldSerialize);
@@ -228,6 +228,30 @@ namespace System.Text.Json
 
         public abstract bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref Utf8JsonReader reader);
 
+        public abstract bool ReadJsonAsObject(ref ReadStack state, ref Utf8JsonReader reader, out object? value);
+
+        public bool ReadJsonExtensionDataValue(ref ReadStack state, ref Utf8JsonReader reader, out object? value)
+        {
+            Debug.Assert(this == state.Current.JsonClassInfo.DataExtensionProperty);
+
+            if (RuntimeClassInfo.ElementType == typeof(object) && reader.TokenType == JsonTokenType.Null)
+            {
+                value = null;
+                return true;
+            }
+
+            JsonConverter<JsonElement> converter = (JsonConverter<JsonElement>)Options.GetConverter(typeof(JsonElement));
+            if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement jsonElement))
+            {
+                // JsonElement is a struct that must be read in full.
+                value = null;
+                return false;
+            }
+
+            value = jsonElement;
+            return true;
+        }
+
         public Type ParentClassType { get; private set; } = null!;
 
         public PropertyInfo? PropertyInfo { get; private set; }
@@ -251,5 +275,6 @@ namespace System.Text.Json
 
         public bool ShouldSerialize { get; private set; }
         public bool ShouldDeserialize { get; private set; }
+        public bool IsIgnored { get; private set; }
     }
 }
index 5c8b466..1ceb16d 100644 (file)
@@ -175,6 +175,33 @@ namespace System.Text.Json
             return success;
         }
 
+        public override bool ReadJsonAsObject(ref ReadStack state, ref Utf8JsonReader reader, out object? value)
+        {
+            bool success;
+            bool isNullToken = reader.TokenType == JsonTokenType.Null;
+            if (isNullToken && !Converter.HandleNullValue && !state.IsContinuation)
+            {
+                value = default(TTypeToConvert)!;
+                success = true;
+            }
+            else
+            {
+                // Optimize for internal converters by avoiding the extra call to TryRead.
+                if (Converter.CanUseDirectReadOrWrite)
+                {
+                    value = Converter.Read(ref reader, RuntimePropertyType!, Options);
+                    return true;
+                }
+                else
+                {
+                    success = Converter.TryRead(ref reader, RuntimePropertyType!, Options, ref state, out TTypeToConvert typedValue);
+                    value = typedValue;
+                }
+            }
+
+            return success;
+        }
+
         public override void SetValueAsObject(object obj, object? value)
         {
             Debug.Assert(HasSetter);
index 701044f..fda63d5 100644 (file)
@@ -23,35 +23,14 @@ namespace System.Text.Json
             ref Utf8JsonReader reader,
             JsonSerializerOptions options,
             ref ReadStack state,
-            out bool useExtensionProperty)
+            out bool useExtensionProperty,
+            bool createExtensionProperty = true)
         {
             Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
 
-            JsonPropertyInfo jsonPropertyInfo;
+            ReadOnlySpan<byte> unescapedPropertyName = GetPropertyName(ref state, ref reader, options);
 
-            ReadOnlySpan<byte> unescapedPropertyName;
-            ReadOnlySpan<byte> propertyName = reader.GetSpan();
-
-            if (reader._stringHasEscaping)
-            {
-                int idx = propertyName.IndexOf(JsonConstants.BackSlash);
-                Debug.Assert(idx != -1);
-                unescapedPropertyName = JsonReaderHelper.GetUnescapedSpan(propertyName, idx);
-            }
-            else
-            {
-                unescapedPropertyName = propertyName;
-            }
-
-            if (options.ReferenceHandling.ShouldReadPreservedReferences())
-            {
-                if (propertyName.Length > 0 && propertyName[0] == '$')
-                {
-                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
-                }
-            }
-
-            jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);
+            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);
 
             // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called.
             state.Current.PropertyIndex++;
@@ -63,12 +42,21 @@ namespace System.Text.Json
                 if (dataExtProperty != null)
                 {
                     state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
-                    CreateDataExtensionProperty(obj, dataExtProperty);
+
+                    if (createExtensionProperty)
+                    {
+                        CreateDataExtensionProperty(obj, dataExtProperty);
+                    }
+
                     jsonPropertyInfo = dataExtProperty;
+                    useExtensionProperty = true;
+                }
+                else
+                {
+                    useExtensionProperty = false;
                 }
 
                 state.Current.JsonPropertyInfo = jsonPropertyInfo;
-                useExtensionProperty = true;
                 return jsonPropertyInfo;
             }
 
@@ -101,7 +89,38 @@ namespace System.Text.Json
             return jsonPropertyInfo;
         }
 
-        private static void CreateDataExtensionProperty(
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static ReadOnlySpan<byte> GetPropertyName(
+            ref ReadStack state,
+            ref Utf8JsonReader reader,
+            JsonSerializerOptions options)
+        {
+            ReadOnlySpan<byte> unescapedPropertyName;
+            ReadOnlySpan<byte> propertyName = reader.GetSpan();
+
+            if (reader._stringHasEscaping)
+            {
+                int idx = propertyName.IndexOf(JsonConstants.BackSlash);
+                Debug.Assert(idx != -1);
+                unescapedPropertyName = JsonReaderHelper.GetUnescapedSpan(propertyName, idx);
+            }
+            else
+            {
+                unescapedPropertyName = propertyName;
+            }
+
+            if (options.ReferenceHandling.ShouldReadPreservedReferences())
+            {
+                if (propertyName.Length > 0 && propertyName[0] == '$')
+                {
+                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
+                }
+            }
+
+            return unescapedPropertyName;
+        }
+
+        internal static void CreateDataExtensionProperty(
             object obj,
             JsonPropertyInfo jsonPropertyInfo)
         {
index 5a30856..ae87d6f 100644 (file)
@@ -11,6 +11,11 @@ namespace System.Text.Json
     {
         public abstract JsonClassInfo.ConstructorDelegate? CreateConstructor(Type classType);
 
+        public abstract JsonClassInfo.ParameterizedConstructorDelegate<T>? CreateParameterizedConstructor<T>(ConstructorInfo constructor);
+
+        public abstract JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>?
+            CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>(ConstructorInfo constructor);
+
         public abstract Action<TCollection, object?> CreateAddMethodDelegate<TCollection>();
 
         public abstract Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TElement, TCollection>();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ParameterRef.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ParameterRef.cs
new file mode 100644 (file)
index 0000000..9f2c654
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Text.Json
+{
+    internal readonly struct ParameterRef
+    {
+        public ParameterRef(ulong key, JsonParameterInfo info)
+        {
+            Key = key;
+            Info = info;
+        }
+
+        // The first 6 bytes are the first part of the name and last 2 bytes are the name's length.
+        public readonly ulong Key;
+
+        public readonly JsonParameterInfo Info;
+    }
+}
index fc603d7..3a9e0dd 100644 (file)
@@ -5,6 +5,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using System.Text.Json.Serialization;
 
 namespace System.Text.Json
@@ -26,6 +27,9 @@ namespace System.Text.Json
 
         private List<ReadStackFrame> _previous;
 
+        // State cache when deserializing objects with parameterized constructors.
+        private List<ArgumentState>? _ctorArgStateCache;
+
         /// <summary>
         /// Bytes consumed in the current loop.
         /// </summary>
@@ -100,7 +104,18 @@ namespace System.Text.Json
                 else
                 {
                     JsonClassInfo jsonClassInfo;
-                    if ((Current.JsonClassInfo.ClassType & (ClassType.Object | ClassType.Value | ClassType.NewValue)) != 0)
+                    if (Current.JsonClassInfo.ClassType == ClassType.Object)
+                    {
+                        if (Current.JsonPropertyInfo != null)
+                        {
+                            jsonClassInfo = Current.JsonPropertyInfo.RuntimeClassInfo;
+                        }
+                        else
+                        {
+                            jsonClassInfo = Current.CtorArgumentState!.JsonParameterInfo!.RuntimeClassInfo;
+                        }
+                    }
+                    else if ((Current.JsonClassInfo.ClassType & (ClassType.Value | ClassType.NewValue)) != 0)
                     {
                         // Although ClassType.Value doesn't push, a custom custom converter may re-enter serialization.
                         jsonClassInfo = Current.JsonPropertyInfo!.RuntimeClassInfo;
@@ -138,6 +153,8 @@ namespace System.Text.Json
                     _count++;
                 }
             }
+
+            SetConstrutorArgumentState();
         }
 
         public void Pop(bool success)
@@ -187,6 +204,8 @@ namespace System.Text.Json
             {
                 Current = _previous[--_count -1];
             }
+
+            SetConstrutorArgumentState();
         }
 
         // Return a JSONPath using simple dot-notation when possible. When special characters are present, bracket-notation is used:
@@ -278,8 +297,9 @@ namespace System.Text.Json
                 byte[]? utf8PropertyName = frame.JsonPropertyName;
                 if (utf8PropertyName == null)
                 {
-                    // Attempt to get the JSON property name from the JsonPropertyInfo.
-                    utf8PropertyName = frame.JsonPropertyInfo?.JsonPropertyName;
+                    // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
+                    utf8PropertyName = frame.JsonPropertyInfo?.JsonPropertyName ??
+                        frame.CtorArgumentState?.JsonParameterInfo?.JsonPropertyName;
                     if (utf8PropertyName == null)
                     {
                         // Attempt to get the JSON property name set manually for dictionary
@@ -296,5 +316,30 @@ namespace System.Text.Json
                 return propertyName;
             }
         }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void SetConstrutorArgumentState()
+        {
+            if (Current.JsonClassInfo.ParameterCount > 0)
+            {
+                // A zero index indicates a new stack frame.
+                if (Current.CtorArgumentStateIndex == 0)
+                {
+                    if (_ctorArgStateCache == null)
+                    {
+                        _ctorArgStateCache = new List<ArgumentState>();
+                    }
+
+                    var newState = new ArgumentState();
+                    _ctorArgStateCache.Add(newState);
+
+                    (Current.CtorArgumentStateIndex, Current.CtorArgumentState) = (_ctorArgStateCache.Count, newState);
+                }
+                else
+                {
+                    Current.CtorArgumentState = _ctorArgStateCache![Current.CtorArgumentStateIndex - 1];
+                }
+            }
+        }
     }
 }
index eba6e43..e7d8582 100644 (file)
@@ -38,6 +38,17 @@ namespace System.Text.Json
         // Add method delegate for Non-generic Stack and Queue; and types that derive from them.
         public object? AddMethodDelegate;
 
+        // Holds relevant state when deserializing objects with parameterized constructors.
+        public int CtorArgumentStateIndex;
+        public ArgumentState? CtorArgumentState;
+
+        public void EndConstructorParameter()
+        {
+            CtorArgumentState!.JsonParameterInfo = null;
+            JsonPropertyName = null;
+            PropertyState = StackFramePropertyState.None;
+        }
+
         public void EndProperty()
         {
             JsonPropertyInfo = null!;
@@ -86,6 +97,8 @@ namespace System.Text.Json
         public void Reset()
         {
             AddMethodDelegate = null;
+            CtorArgumentStateIndex = 0;
+            CtorArgumentState = null;
             JsonClassInfo = null!;
             ObjectState = StackFrameObjectState.None;
             OriginalDepth = 0;
index f37ceca..3e34ada 100644 (file)
@@ -5,6 +5,7 @@
 #if NETFRAMEWORK || NETCOREAPP
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
 using System.Reflection;
 using System.Reflection.Emit;
 
@@ -55,6 +56,110 @@ namespace System.Text.Json.Serialization
             return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
         }
 
+        public override JsonClassInfo.ParameterizedConstructorDelegate<T>? CreateParameterizedConstructor<T>(ConstructorInfo constructor)
+        {
+            Type type = typeof(T);
+
+            Debug.Assert(!type.IsAbstract);
+            // If ctor is non-public, we've verified upstream that it has the [JsonConstructorAttribute].
+            Debug.Assert(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+                .Contains(constructor));
+
+            ParameterInfo[] parameters = constructor.GetParameters();
+            int parameterCount = parameters.Length;
+
+            if (parameterCount > JsonConstants.MaxParameterCount)
+            {
+                return null;
+            }
+
+            var dynamicMethod = new DynamicMethod(
+                ConstructorInfo.ConstructorName,
+                type,
+                new[] { typeof(object[]) },
+                typeof(ReflectionEmitMemberAccessor).Module,
+                skipVisibility: true);
+
+            ILGenerator generator = dynamicMethod.GetILGenerator();
+
+            for (int i = 0; i < parameterCount; i++)
+            {
+                Type paramType = parameters[i].ParameterType;
+
+                generator.Emit(OpCodes.Ldarg_0);
+                generator.Emit(OpCodes.Ldc_I4_S, i);
+                generator.Emit(OpCodes.Ldelem_Ref);
+
+                if (paramType.IsValueType)
+                {
+                    generator.Emit(OpCodes.Unbox_Any, paramType);
+                }
+                else
+                {
+                    generator.Emit(OpCodes.Castclass, paramType);
+                };
+            }
+
+            generator.Emit(OpCodes.Newobj, constructor);
+            generator.Emit(OpCodes.Ret);
+
+            return (JsonClassInfo.ParameterizedConstructorDelegate<T>)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ParameterizedConstructorDelegate<T>));
+        }
+
+        public override JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>?
+            CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>(ConstructorInfo constructor)
+        {
+            Type type = typeof(T);
+
+            Debug.Assert(!type.IsAbstract);
+            // If ctor is non-public, we've verified upstream that it has the [JsonConstructorAttribute].
+            Debug.Assert(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+                .Contains(constructor));
+
+            ParameterInfo[] parameters = constructor.GetParameters();
+            int parameterCount = parameters.Length;
+
+            Debug.Assert(parameterCount <= JsonConstants.UnboxedParameterCountThreshold);
+
+            var dynamicMethod = new DynamicMethod(
+                ConstructorInfo.ConstructorName,
+                type,
+                new[] { typeof(TArg0), typeof(TArg1), typeof(TArg2), typeof(TArg3) },
+                typeof(ReflectionEmitMemberAccessor).Module,
+                skipVisibility: true);
+
+            ILGenerator generator = dynamicMethod.GetILGenerator();
+
+            for (int index = 0; index < parameterCount; index++)
+            {
+                switch (index)
+                {
+                    case 0:
+                        generator.Emit(OpCodes.Ldarg_0);
+                        break;
+                    case 1:
+                        generator.Emit(OpCodes.Ldarg_1);
+                        break;
+                    case 2:
+                        generator.Emit(OpCodes.Ldarg_2);
+                        break;
+                    case 3:
+                        generator.Emit(OpCodes.Ldarg_3);
+                        break;
+                    default:
+                        Debug.Fail("We shouldn't be here if there are more than 4 parameters.");
+                        throw new InvalidOperationException();
+                }
+            }
+
+            generator.Emit(OpCodes.Newobj, constructor);
+            generator.Emit(OpCodes.Ret);
+
+            return (JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>)
+                dynamicMethod.CreateDelegate(
+                    typeof(JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>));
+        }
+
         public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
         {
             Type collectionType = typeof(TCollection);
index ec2bfd6..db8e857 100644 (file)
@@ -4,6 +4,8 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
 using System.Reflection;
 
 namespace System.Text.Json.Serialization
@@ -25,7 +27,92 @@ namespace System.Text.Json.Serialization
                 return null;
             }
 
-            return () => Activator.CreateInstance(type);
+            return () => Activator.CreateInstance(type, nonPublic: false);
+        }
+
+        public override JsonClassInfo.ParameterizedConstructorDelegate<T>? CreateParameterizedConstructor<T>(ConstructorInfo constructor)
+        {
+            Type type = typeof(T);
+
+            Debug.Assert(!type.IsAbstract);
+            // If ctor is non-public, we've verified upstream that it has the [JsonConstructorAttribute].
+            Debug.Assert(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+                .Contains(constructor));
+
+            int parameterCount = constructor.GetParameters().Length;
+
+            if (parameterCount > JsonConstants.MaxParameterCount)
+            {
+                return null;
+            }
+
+            return (arguments) =>
+            {
+                // The input array was rented from the shared ArrayPool, so its size is likely to be larger than the param count.
+                // The emit equivalent of this method does not (need to) allocate here + transfer the objects.
+                object[] argsToPass = new object[parameterCount];
+
+                for (int i = 0; i < parameterCount; i++)
+                {
+                    argsToPass[i] = arguments[i];
+                }
+
+                try
+                {
+                    return (T)constructor.Invoke(argsToPass);
+                }
+                catch (TargetInvocationException e)
+                {
+                    // Plumb ArgumentException through for tuples with more than 7 generic parameters, e.g.
+                    // System.ArgumentException : The last element of an eight element tuple must be a Tuple.
+                    // This doesn't apply to the method below as it supports a max of 4 constructor params.
+                    throw e.InnerException ?? e;
+                }
+            };
+        }
+
+        public override JsonClassInfo.ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>?
+            CreateParameterizedConstructor<T, TArg0, TArg1, TArg2, TArg3>(ConstructorInfo constructor)
+        {
+            Type type = typeof(T);
+
+            Debug.Assert(!type.IsAbstract);
+            // If ctor is non-public, we've verified upstream that it has the [JsonConstructorAttribute].
+            Debug.Assert(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+                .Contains(constructor));
+
+            int parameterCount = constructor.GetParameters().Length;
+
+            Debug.Assert(parameterCount < JsonConstants.UnboxedParameterCountThreshold);
+
+            return (arg0, arg1, arg2, arg3) =>
+            {
+                object[] arguments = new object[parameterCount];
+
+                for (int i = 0; i < parameterCount; i++)
+                {
+                    switch (i)
+                    {
+                        case 0:
+                            arguments[0] = arg0!;
+                            break;
+                        case 1:
+                            arguments[1] = arg1!;
+                            break;
+                        case 2:
+                            arguments[2] = arg2!;
+                            break;
+                        case 3:
+                            arguments[3] = arg3!;
+                            break;
+                        default:
+                            Debug.Fail("We shouldn't be here if there are more than 4 parameters.");
+                            throw new InvalidOperationException();
+                    }
+                }
+
+                return (T)constructor.Invoke(arguments);
+            };
         }
 
         public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
index 20d9924..6ff469e 100644 (file)
@@ -28,6 +28,13 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
+        public static NotSupportedException ThrowNotSupportedException_ConstructorMaxOf64Parameters(ConstructorInfo constructorInfo, Type type)
+        {
+            throw new NotSupportedException(SR.Format(SR.ConstructorMaxOf64Parameters, constructorInfo, type));
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
         {
             var ex = new JsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType));
@@ -121,9 +128,9 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowInvalidOperationException_SerializerPropertyNameConflict(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
+        public static void ThrowInvalidOperationException_SerializerPropertyNameConflict(Type type, JsonPropertyInfo jsonPropertyInfo)
         {
-            throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, jsonClassInfo.Type, jsonPropertyInfo.PropertyInfo?.Name));
+            throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, type, jsonPropertyInfo.PropertyInfo?.Name));
         }
 
         [DoesNotReturn]
@@ -148,6 +155,56 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters(
+            Type parentType,
+            ParameterInfo parameterInfo,
+            PropertyInfo firstMatch,
+            PropertyInfo secondMatch,
+            ConstructorInfo constructorInfo)
+        {
+            throw new InvalidOperationException(
+                SR.Format(
+                    SR.MultipleMembersBindWithConstructorParameter,
+                    firstMatch.Name,
+                    secondMatch.Name,
+                    parentType,
+                    parameterInfo.Name,
+                    constructorInfo));
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(ConstructorInfo constructorInfo, Type parentType)
+        {
+            throw new InvalidOperationException(SR.Format(SR.ConstructorParamIncompleteBinding, constructorInfo, parentType));
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static NotSupportedException ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(
+            PropertyInfo propertyInfo,
+            Type classType,
+            ConstructorInfo constructorInfo)
+        {
+            throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, propertyInfo, classType, constructorInfo));
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotHonored(
+            ReadOnlySpan<byte> propertyName,
+            ref Utf8JsonReader reader,
+            ref ReadStack state)
+        {
+            state.Current.JsonPropertyName = propertyName.ToArray();
+
+            NotSupportedException ex = new NotSupportedException(
+                SR.Format(SR.ObjectWithParameterizedCtorRefMetadataNotHonored, state.Current.JsonClassInfo.Type));
+            ThrowNotSupportedException(state, reader, ex);
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ReThrowWithPath(in ReadStack state, JsonReaderException ex)
         {
             Debug.Assert(ex.Path == null);
@@ -262,9 +319,16 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
+        public static void ThrowInvalidOperationException_SerializationDuplicateTypeAttribute<TAttribute>(Type classType)
         {
-            throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalid, jsonClassInfo.Type, jsonPropertyInfo.PropertyInfo?.Name));
+            throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateTypeAttribute, classType, typeof(Attribute)));
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type type, JsonPropertyInfo jsonPropertyInfo)
+        {
+            throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalid, type, jsonPropertyInfo.PropertyInfo?.Name));
         }
 
         [DoesNotReturn]
@@ -331,7 +395,7 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowNotSupportedException_DeserializeNoParameterlessConstructor(Type invalidType)
+        public static void ThrowNotSupportedException_DeserializeNoDeserializationConstructor(Type invalidType)
         {
             if (invalidType.IsInterface)
             {
@@ -339,7 +403,7 @@ namespace System.Text.Json
             }
             else
             {
-                throw new NotSupportedException(SR.Format(SR.DeserializeMissingParameterlessConstructor, invalidType));
+                throw new NotSupportedException(SR.Format(SR.DeserializeMissingDeserializationConstructor, nameof(JsonConstructorAttribute), invalidType));
             }
         }
 
@@ -454,6 +518,11 @@ namespace System.Text.Json
             ref Utf8JsonReader reader,
             ref ReadStack state)
         {
+            if (state.Current.JsonClassInfo.PropertyInfoForClassInfo.ConverterBase.ConstructorIsParameterized)
+            {
+                ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotHonored(propertyName, ref reader, ref state);
+            }
+
             MetadataPropertyName name = JsonSerializer.GetMetadataPropertyName(propertyName);
             if (name == MetadataPropertyName.Id)
             {
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.AttributePresence.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.AttributePresence.cs
new file mode 100644 (file)
index 0000000..e3b6973
--- /dev/null
@@ -0,0 +1,162 @@
+//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 Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract partial class ConstructorTests
+    {
+        [Fact]
+        public void NonPublicCtors_NotSupported()
+        {
+            void RunTest<T>()
+            {
+                NotSupportedException ex = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<T>("{}"));
+                Assert.Contains("JsonConstructorAttribute", ex.ToString());
+            }
+
+            RunTest<PrivateParameterlessCtor>();
+            RunTest<InternalParameterlessCtor>();
+            RunTest<ProtectedParameterlessCtor>();
+            RunTest<PrivateParameterizedCtor>();
+            RunTest<InternalParameterizedCtor>();
+            RunTest<ProtectedParameterizedCtor>();
+            RunTest<PrivateParameterizedCtor_WithAttribute>();
+            RunTest<InternalParameterizedCtor_WithAttribute>();
+            RunTest<ProtectedParameterizedCtor_WithAttribute>();
+        }
+
+        [Fact]
+        public void SinglePublicParameterizedCtor_SingleParameterlessCtor_NoAttribute_Supported_UseParameterlessCtor()
+        {
+            var obj1 = Serializer.Deserialize<SinglePublicParameterizedCtor>(@"{""MyInt"":1,""MyString"":""1""}");
+            Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj1));
+        }
+
+        [Fact]
+        public void MultiplePublicParameterizedCtors_SingleParameterlessCtor_NoAttribute_Supported_UseParameterlessCtor()
+        {
+            void RunTest<T>()
+            {
+                var obj1 = Serializer.Deserialize<T>(@"{""MyInt"":1,""MyString"":""1""}");
+                Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj1));
+            }
+
+            RunTest<SingleParameterlessCtor_MultiplePublicParameterizedCtor>();
+            RunTest<SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct>();
+        }
+
+        [Fact]
+        public void SinglePublicParameterizedCtor_NoPublicParameterlessCtor_NoAttribute_Supported()
+        {
+            void RunTest<T>()
+            {
+                var obj1 = Serializer.Deserialize<T>(@"{""MyInt"":1}");
+                Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj1));
+            }
+
+            RunTest<PublicParameterizedCtor>();
+            RunTest<PrivateParameterlessConstructor_PublicParameterizedCtor>();
+        }
+
+        [Fact]
+        public void SinglePublicParameterizedCtor_NoPublicParameterlessCtor_WithAttribute_Supported()
+        {
+            void RunTest<T>()
+            {
+                var obj1 = Serializer.Deserialize<T>(@"{""MyInt"":1}");
+                Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj1));
+            }
+
+            RunTest<PublicParameterizedCtor_WithAttribute>();
+            RunTest<Struct_PublicParameterizedConstructor_WithAttribute>();
+            RunTest<PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute>();
+        }
+
+        [Fact]
+        public void Class_MultiplePublicParameterizedCtors_NoPublicParameterlessCtor_NoAttribute_NotSupported()
+        {
+            Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<MultiplePublicParameterizedCtor>(@"{""MyInt"":1,""MyString"":""1""}"));
+        }
+
+        [Fact]
+        public void Struct_MultiplePublicParameterizedCtors_NoPublicParameterlessCtor_NoAttribute_Supported_UseParameterlessCtor()
+        {
+            var obj = Serializer.Deserialize<MultiplePublicParameterizedCtor_Struct>(@"{""myInt"":1,""myString"":""1""}");
+            Assert.Equal(0, obj.MyInt);
+            Assert.Null(obj.MyString);
+            Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj));
+        }
+
+        [Fact]
+        public void NoPublicParameterlessCtor_MultiplePublicParameterizedCtors_WithAttribute_Supported()
+        {
+            var obj1 = Serializer.Deserialize<MultiplePublicParameterizedCtor_WithAttribute>(@"{""MyInt"":1,""MyString"":""1""}");
+            Assert.Equal(1, obj1.MyInt);
+            Assert.Null(obj1.MyString);
+            Assert.Equal(@"{""MyInt"":1,""MyString"":null}", JsonSerializer.Serialize(obj1));
+
+            var obj2 = Serializer.Deserialize<MultiplePublicParameterizedCtor_WithAttribute_Struct>(@"{""MyInt"":1,""MyString"":""1""}");
+            Assert.Equal(1, obj2.MyInt);
+            Assert.Equal("1", obj2.MyString);
+            Assert.Equal(@"{""MyInt"":1,""MyString"":""1""}", JsonSerializer.Serialize(obj2));
+        }
+
+        [Fact]
+        public void PublicParameterlessCtor_MultiplePublicParameterizedCtors_WithAttribute_Supported()
+        {
+            var obj = Serializer.Deserialize<ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute>(@"{""MyInt"":1,""MyString"":""1""}");
+            Assert.Equal(1, obj.MyInt);
+            Assert.Null(obj.MyString);
+            Assert.Equal(@"{""MyInt"":1,""MyString"":null}", JsonSerializer.Serialize(obj));
+        }
+
+        [Fact]
+        public void MultipleAttributes_NotSupported()
+        {
+            void RunTest<T>()
+            {
+                Assert.Throws<InvalidOperationException>(() => Serializer.Deserialize<T>("{}"));
+            }
+
+            RunTest<MultiplePublicParameterizedCtor_WithMultipleAttributes>();
+            RunTest<PublicParameterlessConstructor_PublicParameterizedCtor_WithMultipleAttributes>();
+            RunTest<PrivateParameterlessCtor_InternalParameterizedCtor_WithMultipleAttributes>();
+            RunTest<ProtectedParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes>();
+            RunTest<PublicParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes>();
+            RunTest<PublicParameterizedCtor_PublicParameterizedCtor_WithMultipleAttributes>();
+            RunTest<Struct_PublicParameterizedCtor_PrivateParameterizedCtor_WithMultipleAttributes>();
+            RunTest<Point_2D_Struct_WithMultipleAttributes>();
+            RunTest<Point_2D_Struct_WithMultipleAttributes_OneNonPublic>();
+        }
+
+        [Fact]
+        public void AttributeIgnoredOnIEnumerable()
+        {
+            void RunTest<T>()
+            {
+                Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<T>("[]"));
+            }
+
+            RunTest<Parameterized_StackWrapper>();
+            RunTest<Parameterized_WrapperForICollection>();
+        }
+
+        [Fact]
+        public void Struct_Use_DefaultCtor_ByDefault()
+        {
+            string json = @"{""X"":1,""Y"":2}";
+
+            // By default, serializer uses default ctor to deserializer structs
+            var point1 = Serializer.Deserialize<Point_2D_Struct>(json);
+            Assert.Equal(0, point1.X);
+            Assert.Equal(0, point1.Y);
+
+            var point2 = Serializer.Deserialize<Point_2D_Struct_WithAttribute>(json);
+            Assert.Equal(1, point2.X);
+            Assert.Equal(2, point2.Y);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Cache.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Cache.cs
new file mode 100644 (file)
index 0000000..f8797d9
--- /dev/null
@@ -0,0 +1,162 @@
+// 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.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract partial class ConstructorTests
+    {
+        [Fact, OuterLoop]
+        public void MultipleThreadsLooping()
+        {
+            const int Iterations = 100;
+
+            for (int i = 0; i < Iterations; i++)
+            {
+                MultipleThreads();
+            }
+        }
+
+        [Fact]
+        public void MultipleThreads()
+        {
+            // Use local options to avoid obtaining already cached metadata from the default options.
+            var options = new JsonSerializerOptions();
+
+            // Verify the test class has >32 properties since that is a threshold for using the fallback dictionary.
+            Assert.True(typeof(ClassWithConstructor_SimpleAndComplexParameters).GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 32);
+
+            void DeserializeObjectMinimal()
+            {
+                var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MyDecimal"" : 3.3}", options);
+            };
+
+            void DeserializeObjectFlipped()
+            {
+                var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(
+                    ClassWithConstructor_SimpleAndComplexParameters.s_json_flipped, options);
+                obj.Verify();
+            };
+
+            void DeserializeObjectNormal()
+            {
+                var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(
+                    ClassWithConstructor_SimpleAndComplexParameters.s_json, options);
+                obj.Verify();
+            };
+
+            void SerializeObject()
+            {
+                var obj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+                JsonSerializer.Serialize(obj, options);
+            };
+
+            const int ThreadCount = 8;
+            const int ConcurrentTestsCount = 4;
+            Task[] tasks = new Task[ThreadCount * ConcurrentTestsCount];
+
+            for (int i = 0; i < tasks.Length; i += ConcurrentTestsCount)
+            {
+                // Create race condition to populate the sorted property cache with different json ordering.
+                tasks[i + 0] = Task.Run(() => DeserializeObjectMinimal());
+                tasks[i + 1] = Task.Run(() => DeserializeObjectFlipped());
+                tasks[i + 2] = Task.Run(() => DeserializeObjectNormal());
+
+                // Ensure no exceptions on serialization
+                tasks[i + 3] = Task.Run(() => SerializeObject());
+            };
+
+            Task.WaitAll(tasks);
+        }
+
+        [Fact]
+        public void PropertyCacheWithMinInputsFirst()
+        {
+            // Use local options to avoid obtaining already cached metadata from the default options.
+            var options = new JsonSerializerOptions();
+
+            string json = "{}";
+            Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+
+            ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+            testObj.Verify();
+
+            json = JsonSerializer.Serialize(testObj, options);
+            testObj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            testObj.Verify();
+        }
+
+        [Fact]
+        public void PropertyCacheWithMinInputsLast()
+        {
+            // Use local options to avoid obtaining already cached metadata from the default options.
+            var options = new JsonSerializerOptions();
+
+            ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance();
+            testObj.Verify();
+
+            string json = JsonSerializer.Serialize(testObj, options);
+            testObj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+            testObj.Verify();
+
+            json = "{}";
+            Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(json, options);
+        }
+
+        // Use a common options instance to encourage additional metadata collisions across types. Also since
+        // this options is not the default options instance the tests will not use previously cached metadata.
+        private JsonSerializerOptions s_options = new JsonSerializerOptions();
+
+        [Fact]
+        public void MultipleTypes()
+        {
+            void Serialize<T>(object[] args)
+            {
+                Type type = typeof(T);
+
+                T localTestObj = (T)Activator.CreateInstance(type, args);
+                ((ITestClass)localTestObj).Initialize();
+                ((ITestClass)localTestObj).Verify();
+                string json = JsonSerializer.Serialize(localTestObj, s_options);
+            };
+
+            void Deserialize<T>(string json)
+            {
+                ITestClass obj = (ITestClass)Serializer.Deserialize<T>(json, s_options);
+                obj.Verify();
+            };
+
+            void RunTest<T>(T testObj, object[] args)
+            {
+                // Get the test json with the default options to avoid cache pollution of Deserialize() below.
+                ((ITestClass)testObj).Initialize();
+                ((ITestClass)testObj).Verify();
+                string json = JsonSerializer.Serialize(testObj);
+
+                const int ThreadCount = 12;
+                const int ConcurrentTestsCount = 2;
+                Task[] tasks = new Task[ThreadCount * ConcurrentTestsCount];
+
+                for (int i = 0; i < tasks.Length; i += ConcurrentTestsCount)
+                {
+                    tasks[i + 0] = Task.Run(() => Deserialize<T>(json));
+                    tasks[i + 1] = Task.Run(() => Serialize<T>(args));
+                };
+
+                Task.WaitAll(tasks);
+            }
+
+            RunTest<Point_2D>(new Point_2D(1, 2), new object[] { 1, 2 });
+            RunTest(new Point_3D(1, 2, 3), new object[] { 1, 2, 3 });
+            RunTest(new Point_2D_With_ExtData(1, 2), new object[] { 1, 2 });
+
+            Guid id = Guid.Parse("270bb22b-4816-4bd9-9acd-8ec5b1a896d3");
+            RunTest(new Parameterized_Person_Simple(id), new object[] { id });
+            RunTest(new Point_MembersHave_JsonPropertyName(1, 2), new object[] { 1, 2 });
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Exceptions.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Exceptions.cs
new file mode 100644 (file)
index 0000000..e47b1e9
--- /dev/null
@@ -0,0 +1,338 @@
+// 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.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract partial class ConstructorTests
+    {
+        [Fact]
+        public void MultipleProperties_Cannot_BindTo_TheSame_ConstructorParameter()
+        {
+            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
+                () => Serializer.Deserialize<Point_MultipleMembers_BindTo_OneConstructorParameter>("{}"));
+
+            string exStr = ex.ToString();
+            Assert.Contains("'X'", exStr);
+            Assert.Contains("'x'", exStr);
+            Assert.Contains("(Int32, Int32)", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.Point_MultipleMembers_BindTo_OneConstructorParameter", exStr);
+
+            ex = Assert.Throws<InvalidOperationException>(
+                () => Serializer.Deserialize<Point_MultipleMembers_BindTo_OneConstructorParameter_Variant>("{}"));
+
+            exStr = ex.ToString();
+            Assert.Contains("'X'", exStr);
+            Assert.Contains("'x'", exStr);
+            Assert.Contains("(Int32)", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.Point_MultipleMembers_BindTo_OneConstructorParameter_Variant", exStr);
+        }
+
+        [Fact]
+        public void All_ConstructorParameters_MustBindTo_ObjectMembers()
+        {
+            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
+                () => Serializer.Deserialize<Point_Without_Members>("{}"));
+
+            string exStr = ex.ToString();
+            Assert.Contains("(Int32, Int32)", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.Point_Without_Members", exStr);
+
+            ex = Assert.Throws<InvalidOperationException>(
+                () => Serializer.Deserialize<Point_With_MismatchedMembers>("{}"));
+            exStr = ex.ToString();
+            Assert.Contains("(Int32, Int32)", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.Point_With_MismatchedMembers", exStr);
+
+            ex = Assert.Throws<InvalidOperationException>(
+                () => Serializer.Deserialize<WrapperFor_Point_With_MismatchedMembers>(@"{""MyInt"":1,""MyPoint"":{}}"));
+            exStr = ex.ToString();
+            Assert.Contains("(Int32, Int32)", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.Point_With_MismatchedMembers", exStr);
+        }
+
+        [Fact]
+        public void LeadingReferenceMetadataNotSupported()
+        {
+            string json = @"{""$id"":""1"",""Name"":""Jet"",""Manager"":{""$ref"":""1""}}";
+
+            // Metadata ignored by default.
+            var employee = Serializer.Deserialize<Employee>(json);
+
+            Assert.Equal("Jet", employee.Name);
+            Assert.Null(employee.Manager.Name); ;
+            Assert.Null(employee.Manager.Manager);
+
+            // Metadata not supported with preserve ref feature on.
+
+            var options = new JsonSerializerOptions { ReferenceHandling = ReferenceHandling.Preserve };
+
+            NotSupportedException ex = Assert.Throws<NotSupportedException>(
+                () => Serializer.Deserialize<Employee>(json, options));
+
+            string exStr = ex.ToString();
+            Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr);
+            Assert.Contains("$.$id", exStr);
+        }
+
+        private class Employee
+        {
+            public string Name { get; }
+            public Employee Manager { get; set; }
+
+            public Employee(string name)
+            {
+                Name = name;
+            }
+        }
+
+        [Fact]
+        public void RandomReferenceMetadataNotSupported()
+        {
+            string json = @"{""Name"":""Jet"",""$random"":10}";
+
+            // Baseline, preserve ref feature off.
+
+            var employee = JsonSerializer.Deserialize<Employee>(json);
+
+            Assert.Equal("Jet", employee.Name);
+
+            // Metadata not supported with preserve ref feature on.
+
+            var options = new JsonSerializerOptions { ReferenceHandling = ReferenceHandling.Preserve };
+
+            NotSupportedException ex = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<Employee>(json, options));
+            string exStr = ex.ToString();
+            Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr);
+            Assert.Contains("$.$random", exStr);
+        }
+
+        [Fact]
+        public void ExtensionDataProperty_CannotBindTo_CtorParam()
+        {
+            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => Serializer.Deserialize<Class_ExtData_CtorParam>("{}"));
+            string exStr = ex.ToString();
+            Assert.Contains("System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData", exStr);
+            Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam", exStr);
+            Assert.Contains("(System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement])", exStr);
+        }
+
+        public class Class_ExtData_CtorParam
+        {
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> ExtensionData { get; set; }
+
+            public Class_ExtData_CtorParam(Dictionary<string, JsonElement> extensionData) { }
+        }
+
+        [Fact]
+        public void AnonymousObject_InvalidOperationException()
+        {
+            var obj = new { Prop = 5 };
+
+            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize("{}", obj.GetType()));
+
+            // We expect property 'Prop' to bind with a ctor arg called 'prop', but the ctor arg is called 'Prop'.
+            string exStr = ex.ToString();
+            Assert.Contains("AnonymousType", exStr);
+            Assert.Contains("(Int32)", exStr);
+            Assert.Contains("[System.Int32]", exStr);
+        }
+
+        [Fact]
+        public void DeserializePathForObjectFails()
+        {
+            const string GoodJson = "{\"Property\u04671\":1}";
+            const string GoodJsonEscaped = "{\"Property\\u04671\":1}";
+            const string BadJson = "{\"Property\u04671\":bad}";
+            const string BadJsonEscaped = "{\"Property\\u04671\":bad}";
+            const string Expected = "$.Property\u04671";
+
+            ClassWithUnicodePropertyName obj;
+
+            // Baseline.
+            obj = Serializer.Deserialize<ClassWithUnicodePropertyName>(GoodJson);
+            Assert.Equal(1, obj.Property\u04671);
+
+            obj = Serializer.Deserialize<ClassWithUnicodePropertyName>(GoodJsonEscaped);
+            Assert.Equal(1, obj.Property\u04671);
+
+            JsonException e;
+
+            // Exception.
+            e = Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWithUnicodePropertyName>(BadJson));
+            Assert.Equal(Expected, e.Path);
+
+            e = Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWithUnicodePropertyName>(BadJsonEscaped));
+            Assert.Equal(Expected, e.Path);
+        }
+
+        private class ClassWithUnicodePropertyName
+        {
+            public int Property\u04671 { get; } // contains a trailing "1"
+
+            public ClassWithUnicodePropertyName(int property\u04671)
+            {
+                Property\u04671 = property\u04671;
+            }
+        }
+
+        [Fact]
+        public void PathForChildPropertyFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<RootClass>(@"{""Child"":{""MyInt"":bad]}"));
+            Assert.Equal("$.Child.MyInt", e.Path);
+        }
+
+        public class RootClass
+        {
+            public ChildClass Child { get; }
+
+            public RootClass(ChildClass child)
+            {
+                Child = child;
+            }
+        }
+
+        public class ChildClass
+        {
+            public int MyInt { get; set; }
+            public int[] MyIntArray { get; set; }
+            public Dictionary<string, ChildClass> MyDictionary { get; set; }
+            public ChildClass[] Children { get; set; }
+        }
+
+        [Fact]
+        public void PathForChildListFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<RootClass>(@"{""Child"":{""MyIntArray"":[1, bad]}"));
+            Assert.Contains("$.Child.MyIntArray", e.Path);
+        }
+
+        [Fact]
+        public void PathForChildDictionaryFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<RootClass>(@"{""Child"":{""MyDictionary"":{""Key"": bad]"));
+            Assert.Equal("$.Child.MyDictionary.Key", e.Path);
+        }
+
+        [Fact]
+        public void PathForSpecialCharacterFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<RootClass>(@"{""Child"":{""MyDictionary"":{""Key1"":{""Children"":[{""MyDictionary"":{""K.e.y"":"""));
+            Assert.Equal("$.Child.MyDictionary.Key1.Children[0].MyDictionary['K.e.y']", e.Path);
+        }
+
+        [Fact]
+        public void PathForSpecialCharacterNestedFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<RootClass>(@"{""Child"":{""Children"":[{}, {""MyDictionary"":{""K.e.y"": {""MyInt"":bad"));
+            Assert.Equal("$.Child.Children[1].MyDictionary['K.e.y'].MyInt", e.Path);
+        }
+
+        [Fact]
+        public void EscapingFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<Parameterized_ClassWithUnicodeProperty>("{\"A\u0467\":bad}"));
+            Assert.Equal("$.A\u0467", e.Path);
+        }
+
+        public class Parameterized_ClassWithUnicodeProperty
+        {
+            public int A\u0467 { get; }
+
+            public Parameterized_ClassWithUnicodeProperty(int a\u0467)
+            {
+                A\u0467 = a\u0467;
+            }
+        }
+
+        [Fact]
+        [ActiveIssue("JsonElement needs to support Path")]
+        public void ExtensionPropertyRoundTripFails()
+        {
+            JsonException e = Assert.Throws<JsonException>(() => Serializer.Deserialize<Parameterized_ClassWithExtensionProperty>(@"{""MyNestedClass"":{""UnknownProperty"":bad}}"));
+            Assert.Equal("$.MyNestedClass.UnknownProperty", e.Path);
+        }
+
+        private class Parameterized_ClassWithExtensionProperty
+        {
+            public SimpleTestClass MyNestedClass { get; }
+            public int MyInt { get; }
+
+            [JsonExtensionData]
+            public IDictionary<string, JsonElement> MyOverflow { get; set; }
+
+            public Parameterized_ClassWithExtensionProperty(SimpleTestClass myNestedClass, int myInt)
+            {
+                MyNestedClass = myNestedClass;
+                MyInt = myInt;
+            }
+        }
+
+        [Fact]
+        public void CaseInsensitiveFails()
+        {
+            var options = new JsonSerializerOptions();
+            options.PropertyNameCaseInsensitive = true;
+
+            // Baseline (no exception)
+            {
+                var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":1}", options);
+                Assert.Equal(1, obj.MyDecimal);
+            }
+
+            {
+                var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":1}", options);
+                Assert.Equal(1, obj.MyDecimal);
+            }
+
+            JsonException e;
+
+            e = Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(@"{""mydecimal"":bad}", options));
+            Assert.Equal("$.mydecimal", e.Path);
+
+            e = Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(@"{""MYDECIMAL"":bad}", options));
+            Assert.Equal("$.MYDECIMAL", e.Path);
+        }
+
+        [Fact]
+        public void ClassWithUnsupportedCollectionTypes()
+        {
+            Exception e;
+
+            e = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<ClassWithInvalidArray>(@"{""UnsupportedArray"":[]}"));
+            Assert.Contains("System.Int32[,]", e.ToString());
+            // The exception for element types do not contain the parent type and the property name
+            // since the verification occurs later and is no longer bound to the parent type.
+            Assert.DoesNotContain("ClassWithInvalidArray.UnsupportedArray", e.ToString());
+
+            e = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<ClassWithInvalidDictionary>(@"{""UnsupportedDictionary"":{}}"));
+            Assert.Contains("System.Int32[,]", e.ToString());
+            Assert.DoesNotContain("ClassWithInvalidDictionary.UnsupportedDictionary", e.ToString());
+        }
+
+        private class ClassWithInvalidArray
+        {
+            public int[,] UnsupportedArray { get; set; }
+
+            public ClassWithInvalidArray(int[,] unsupportedArray)
+            {
+                UnsupportedArray = unsupportedArray;
+            }
+        }
+
+        private class ClassWithInvalidDictionary
+        {
+            public Dictionary<string, int[,]> UnsupportedDictionary { get; set; }
+
+            public ClassWithInvalidDictionary(Dictionary<string, int[,]> unsupportedDictionary)
+            {
+                UnsupportedDictionary = unsupportedDictionary;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.ParameterMatching.cs
new file mode 100644 (file)
index 0000000..edb7090
--- /dev/null
@@ -0,0 +1,1002 @@
+// 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.Generic;
+using System.Collections.Specialized;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public class ConstructorTests_StringTValue : ConstructorTests
+    {
+        public ConstructorTests_StringTValue() : base(DeserializationWrapper.StringTValueSerializer) { }
+    }
+
+    public class ConstructorTests_StreamTValue : ConstructorTests
+    {
+        public ConstructorTests_StreamTValue() : base(DeserializationWrapper.StreamTValueSerializer) { }
+    }
+
+    public abstract partial class ConstructorTests
+    {
+        private DeserializationWrapper Serializer { get; }
+
+        public ConstructorTests(DeserializationWrapper serializer)
+        {
+            Serializer = serializer;
+        }
+
+        [Fact]
+        public void ReturnNullForNullObjects()
+        {
+            Assert.Null(Serializer.Deserialize<Point_2D>("null"));
+            Assert.Null(Serializer.Deserialize<Point_3D>("null"));
+        }
+
+        [Fact]
+        public void JsonExceptionWhenAssigningNullToStruct()
+        {
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D_With_ExtData>("null"));
+        }
+
+        [Fact]
+        public void MatchJsonPropertyToConstructorParameters()
+        {
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""X"":1,""Y"":2}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""Y"":2,""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+        }
+
+        [Fact]
+        public void UseDefaultValues_When_NoJsonMatch()
+        {
+            // Using CLR value when `ParameterInfo.DefaultValue` is not set.
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""x"":1,""y"":2}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""y"":2,""x"":1}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""x"":1,""Y"":2}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""y"":2,""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""Y"":2}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""Y"":2}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(0, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""a"":1,""b"":2}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(0, point.Y);
+
+            // Using `ParameterInfo.DefaultValue` when set; using CLR value as fallback.
+            Point_3D point3d = Serializer.Deserialize<Point_3D>(@"{""X"":1}");
+            Assert.Equal(1, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""y"":2}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""Z"":3}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(3, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""X"":1}");
+            Assert.Equal(1, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""Y"":2}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(2, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""Z"":3}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(3, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""x"":1,""Y"":2}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(2, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""Z"":3,""y"":2}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(3, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""x"":1,""Z"":3}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(3, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+
+            point3d = Serializer.Deserialize<Point_3D>(@"{""a"":1,""b"":2}");
+            Assert.Equal(0, point3d.X);
+            Assert.Equal(0, point3d.Y);
+            Assert.Equal(50, point3d.Z);
+        }
+
+        [Fact]
+        public void CaseInsensitivityWorks()
+        {
+            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""x"":1,""y"":2}", options);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""y"":2,""x"":1}", options);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""x"":1,""Y"":2}", options);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+
+            point = Serializer.Deserialize<Point_2D>(@"{""y"":2,""X"":1}", options);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+        }
+
+        [Fact]
+        public void VaryingOrderingOfJson()
+        {
+            Point_3D point = Serializer.Deserialize<Point_3D>(@"{""X"":1,""Y"":2,""Z"":3}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+
+            point = Serializer.Deserialize<Point_3D>(@"{""X"":1,""Z"":3,""Y"":2}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+
+            point = Serializer.Deserialize<Point_3D>(@"{""Y"":2,""Z"":3,""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+
+            point = Serializer.Deserialize<Point_3D>(@"{""Y"":2,""X"":1,""Z"":3}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+
+            point = Serializer.Deserialize<Point_3D>(@"{""Z"":3,""Y"":2,""X"":1}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+
+            point = Serializer.Deserialize<Point_3D>(@"{""Z"":3,""X"":1,""Y"":2}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+        }
+
+        [Fact]
+        public void AsListElement()
+        {
+            List<Point_3D> list = Serializer.Deserialize<List<Point_3D>>(@"[{""Y"":2,""Z"":3,""X"":1},{""Z"":10,""Y"":30,""X"":20}]");
+            Assert.Equal(1, list[0].X);
+            Assert.Equal(2, list[0].Y);
+            Assert.Equal(3, list[0].Z);
+            Assert.Equal(20, list[1].X);
+            Assert.Equal(30, list[1].Y);
+            Assert.Equal(10, list[1].Z);
+        }
+
+        [Fact]
+        public void AsDictionaryValue()
+        {
+            Dictionary<string, Point_3D> dict = Serializer.Deserialize<Dictionary<string, Point_3D>>(@"{""0"":{""Y"":2,""Z"":3,""X"":1},""1"":{""Z"":10,""Y"":30,""X"":20}}");
+            Assert.Equal(1, dict["0"].X);
+            Assert.Equal(2, dict["0"].Y);
+            Assert.Equal(3, dict["0"].Z);
+            Assert.Equal(20, dict["1"].X);
+            Assert.Equal(30, dict["1"].Y);
+            Assert.Equal(10, dict["1"].Z);
+        }
+
+        [Fact]
+        public void AsProperty_Of_ObjectWithParameterlessCtor()
+        {
+            WrapperForPoint_3D obj = Serializer.Deserialize<WrapperForPoint_3D>(@"{""Point_3D"":{""Y"":2,""Z"":3,""X"":1}}");
+            Point_3D point = obj.Point_3D;
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+        }
+
+        [Fact]
+        public void AsProperty_Of_ObjectWithParameterizedCtor()
+        {
+            ClassWrapperForPoint_3D obj = Serializer.Deserialize<ClassWrapperForPoint_3D>(@"{""Point3D"":{""Y"":2,""Z"":3,""X"":1}}");
+            Point_3D point = obj.Point3D;
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(3, point.Z);
+        }
+
+        [Fact]
+        public void At_Symbol_As_ParameterNamePrefix()
+        {
+            ClassWrapper_For_Int_String obj = Serializer.Deserialize<ClassWrapper_For_Int_String>(@"{""Int"":1,""String"":""1""}");
+            Assert.Equal(1, obj.Int);
+            Assert.Equal("1", obj.String);
+        }
+
+        [Fact]
+        public void At_Symbol_As_ParameterNamePrefix_UseDefaultValues()
+        {
+            ClassWrapper_For_Int_String obj = Serializer.Deserialize<ClassWrapper_For_Int_String>(@"{""@Int"":1,""@String"":""1""}");
+            Assert.Equal(0, obj.Int);
+            Assert.Null(obj.String);
+        }
+
+        [Fact]
+        public void PassDefaultValueToComplexStruct()
+        {
+            ClassWrapperForPoint_3D obj = Serializer.Deserialize<ClassWrapperForPoint_3D>(@"{}");
+            Assert.True(obj.Point3D == default);
+
+            ClassWrapper_For_Int_Point_3D_String obj1 = Serializer.Deserialize<ClassWrapper_For_Int_Point_3D_String>(@"{}");
+            Assert.Equal(0, obj1.MyInt);
+            Assert.Equal(0, obj1.MyPoint3DStruct.X);
+            Assert.Equal(0, obj1.MyPoint3DStruct.Y);
+            Assert.Equal(0, obj1.MyPoint3DStruct.Z);
+            Assert.Null(obj1.MyString);
+        }
+
+        [Fact]
+        public void Null_AsArgument_To_ParameterThat_CanBeNull()
+        {
+            ClassWrapper_For_Int_Point_3D_String obj1 = Serializer.Deserialize<ClassWrapper_For_Int_Point_3D_String>(@"{""MyInt"":1,""MyPoint3DStruct"":{},""MyString"":null}");
+            Assert.Equal(1, obj1.MyInt);
+            Assert.Equal(0, obj1.MyPoint3DStruct.X);
+            Assert.Equal(0, obj1.MyPoint3DStruct.Y);
+            Assert.Equal(50, obj1.MyPoint3DStruct.Z);
+            Assert.Null(obj1.MyString);
+        }
+
+        [Fact]
+        public void Null_AsArgument_To_ParameterThat_CanNotBeNull()
+        {
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWrapper_For_Int_Point_3D_String>(@"{""MyInt"":null,""MyString"":""1""}"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<ClassWrapper_For_Int_Point_3D_String>(@"{""MyPoint3DStruct"":null,""MyString"":""1""}"));
+        }
+
+        [Fact]
+        public void OtherPropertiesAreSet()
+        {
+            var personClass = Serializer.Deserialize<Person_Class>(Person_Class.s_json);
+            personClass.Verify();
+
+            var personStruct = Serializer.Deserialize<Person_Struct>(Person_Struct.s_json);
+            personStruct.Verify();
+        }
+
+        [Fact]
+        public void ExtraProperties_AreIgnored()
+        {
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{ ""x"":1,""y"":2,""b"":3}");
+            Assert.Equal(0, point.X);
+            Assert.Equal(0, point.Y);
+        }
+
+        [Fact]
+        public void ExtraProperties_GoInExtensionData_IfPresent()
+        {
+            Point_2D_With_ExtData point = Serializer.Deserialize<Point_2D_With_ExtData>(@"{""X"":1,""y"":2,""b"":3}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.ExtensionData["y"].GetInt32());
+            Assert.Equal(3, point.ExtensionData["b"].GetInt32());
+        }
+
+        [Fact]
+        public void PropertiesNotSet_WhenJSON_MapsToConstructorParameters()
+        {
+            var obj = Serializer.Deserialize<Point_CtorsIgnoreJson>(@"{""X"":1,""Y"":2}");
+            Assert.Equal(40, obj.X); // Would be 1 if property were set directly after object construction.
+            Assert.Equal(60, obj.Y); // Would be 2 if property were set directly after object construction.
+        }
+
+        [Fact]
+        public void IgnoreNullValues_DontSetNull_ToConstructorArguments_ThatCantBeNull()
+        {
+            // Default is to throw JsonException when null applied to types that can't be null.
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<NullArgTester>(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<NullArgTester>(@"{""Point3DStruct"":null}"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<NullArgTester>(@"{""Int"":null}"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<NullArgTester>(@"{""ImmutableArray"":null}"));
+
+            // Set arguments to default values when IgnoreNullValues is on.
+            var options = new JsonSerializerOptions { IgnoreNullValues = true };
+            var obj = Serializer.Deserialize<NullArgTester>(@"{""Int"":null,""Point3DStruct"":null,""ImmutableArray"":null}", options);
+            Assert.Equal(0, obj.Point3DStruct.X);
+            Assert.Equal(0, obj.Point3DStruct.Y);
+            Assert.Equal(0, obj.Point3DStruct.Z);
+            Assert.True(obj.ImmutableArray.IsDefault);
+            Assert.Equal(50, obj.Int);
+        }
+
+        [Fact]
+        public void NumerousSimpleAndComplexParameters()
+        {
+            var obj = Serializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json);
+            obj.Verify();
+        }
+
+        [Fact]
+        public void ClassWithPrimitives_Parameterless()
+        {
+            var point = new Parameterless_ClassWithPrimitives();
+
+            point.FirstInt = 348943;
+            point.SecondInt = 348943;
+            point.FirstString = "934sdkjfskdfssf";
+            point.SecondString = "sdad9434243242";
+            point.FirstDateTime = DateTime.Now;
+            point.SecondDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            point.X = 234235;
+            point.Y = 912874;
+            point.Z = 434934;
+
+            point.ThirdInt = 348943;
+            point.FourthInt = 348943;
+            point.ThirdString = "934sdkjfskdfssf";
+            point.FourthString = "sdad9434243242";
+            point.ThirdDateTime = DateTime.Now;
+            point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            string json = JsonSerializer.Serialize(point);
+
+            var deserialized = Serializer.Deserialize<Parameterless_ClassWithPrimitives>(json);
+            Assert.Equal(point.FirstInt, deserialized.FirstInt);
+            Assert.Equal(point.SecondInt, deserialized.SecondInt);
+            Assert.Equal(point.FirstString, deserialized.FirstString);
+            Assert.Equal(point.SecondString, deserialized.SecondString);
+            Assert.Equal(point.FirstDateTime, deserialized.FirstDateTime);
+            Assert.Equal(point.SecondDateTime, deserialized.SecondDateTime);
+
+            Assert.Equal(point.X, deserialized.X);
+            Assert.Equal(point.Y, deserialized.Y);
+            Assert.Equal(point.Z, deserialized.Z);
+
+            Assert.Equal(point.ThirdInt, deserialized.ThirdInt);
+            Assert.Equal(point.FourthInt, deserialized.FourthInt);
+            Assert.Equal(point.ThirdString, deserialized.ThirdString);
+            Assert.Equal(point.FourthString, deserialized.FourthString);
+            Assert.Equal(point.ThirdDateTime, deserialized.ThirdDateTime);
+            Assert.Equal(point.FourthDateTime, deserialized.FourthDateTime);
+        }
+
+        [Fact]
+        public void ClassWithPrimitives()
+        {
+            var point = new Parameterized_ClassWithPrimitives_3Args(x: 234235, y: 912874, z: 434934);
+
+            point.FirstInt = 348943;
+            point.SecondInt = 348943;
+            point.FirstString = "934sdkjfskdfssf";
+            point.SecondString = "sdad9434243242";
+            point.FirstDateTime = DateTime.Now;
+            point.SecondDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            point.ThirdInt = 348943;
+            point.FourthInt = 348943;
+            point.ThirdString = "934sdkjfskdfssf";
+            point.FourthString = "sdad9434243242";
+            point.ThirdDateTime = DateTime.Now;
+            point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            string json = JsonSerializer.Serialize(point);
+
+            var deserialized = Serializer.Deserialize<Parameterized_ClassWithPrimitives_3Args>(json);
+            Assert.Equal(point.FirstInt, deserialized.FirstInt);
+            Assert.Equal(point.SecondInt, deserialized.SecondInt);
+            Assert.Equal(point.FirstString, deserialized.FirstString);
+            Assert.Equal(point.SecondString, deserialized.SecondString);
+            Assert.Equal(point.FirstDateTime, deserialized.FirstDateTime);
+            Assert.Equal(point.SecondDateTime, deserialized.SecondDateTime);
+
+            Assert.Equal(point.X, deserialized.X);
+            Assert.Equal(point.Y, deserialized.Y);
+            Assert.Equal(point.Z, deserialized.Z);
+
+            Assert.Equal(point.ThirdInt, deserialized.ThirdInt);
+            Assert.Equal(point.FourthInt, deserialized.FourthInt);
+            Assert.Equal(point.ThirdString, deserialized.ThirdString);
+            Assert.Equal(point.FourthString, deserialized.FourthString);
+            Assert.Equal(point.ThirdDateTime, deserialized.ThirdDateTime);
+            Assert.Equal(point.FourthDateTime, deserialized.FourthDateTime);
+        }
+
+        [Fact]
+        public void ClassWithPrimitivesPerf()
+        {
+            var point = new Parameterized_ClassWithPrimitives_3Args(x: 234235, y: 912874, z: 434934);
+
+            point.FirstInt = 348943;
+            point.SecondInt = 348943;
+            point.FirstString = "934sdkjfskdfssf";
+            point.SecondString = "sdad9434243242";
+            point.FirstDateTime = DateTime.Now;
+            point.SecondDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            point.ThirdInt = 348943;
+            point.FourthInt = 348943;
+            point.ThirdString = "934sdkjfskdfssf";
+            point.FourthString = "sdad9434243242";
+            point.ThirdDateTime = DateTime.Now;
+            point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1);
+
+            string json = JsonSerializer.Serialize(point);
+
+            Serializer.Deserialize<Parameterized_ClassWithPrimitives_3Args>(json);
+            Serializer.Deserialize<Parameterized_ClassWithPrimitives_3Args>(json);
+        }
+
+        [Fact]
+        public void TupleDeserializationWorks()
+        {
+            var tuple = Serializer.Deserialize<Tuple<string, double>>(@"{""Item1"":""New York"",""Item2"":32.68}");
+            Assert.Equal("New York", tuple.Item1);
+            Assert.Equal(32.68, tuple.Item2);
+
+            var tupleWrapper = Serializer.Deserialize<TupleWrapper>(@"{""Tuple"":{""Item1"":""New York"",""Item2"":32.68}}");
+            tuple = tupleWrapper.Tuple;
+            Assert.Equal("New York", tuple.Item1);
+            Assert.Equal(32.68, tuple.Item2);
+
+            var tupleList = Serializer.Deserialize<List<Tuple<string, double>>>(@"[{""Item1"":""New York"",""Item2"":32.68}]");
+            tuple = tupleList[0];
+            Assert.Equal("New York", tuple.Item1);
+            Assert.Equal(32.68, tuple.Item2);
+        }
+
+        [Fact]
+        public void TupleDeserialization_MoreThanSevenItems()
+        {
+            // Seven is okay
+            string json = JsonSerializer.Serialize(Tuple.Create(1, 2, 3, 4, 5, 6, 7));
+            var obj = Serializer.Deserialize<Tuple<int, int, int, int, int, int, int>>(json);
+            Assert.Equal(json, JsonSerializer.Serialize(obj));
+
+            // More than seven arguments needs special casing and can be revisted.
+            // Newtonsoft.Json fails in the same way.
+            json = JsonSerializer.Serialize(Tuple.Create(1, 2, 3, 4, 5, 6, 7, 8));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Tuple<int, int, int, int, int, int, int, int>>(json));
+
+            // Invalid JSON representing a tuple with more than seven items yields an ArgumentException from the constructor.
+            // System.ArgumentException : The last element of an eight element tuple must be a Tuple.
+            // We pass the number 8, not a new Tuple<int>(8).
+            // Fixing this needs special casing. Newtonsoft behaves the same way.
+            string invalidJson = @"{""Item1"":1,""Item2"":2,""Item3"":3,""Item4"":4,""Item5"":5,""Item6"":6,""Item7"":7,""Item1"":8}";
+            Assert.Throws<ArgumentException>(() => Serializer.Deserialize<Tuple<int, int, int, int, int, int, int, int>>(invalidJson));
+        }
+
+        [Fact]
+        public void TupleDeserialization_DefaultValuesUsed_WhenJsonMissing()
+        {
+            // Seven items; only three provided.
+            string input = @"{""Item2"":""2"",""Item3"":3,""Item6"":6}";
+            var obj = Serializer.Deserialize<Tuple<int, string, int, string, string, int, Point_3D_Struct>>(input);
+
+            string serialized = JsonSerializer.Serialize(obj);
+            Assert.Contains(@"""Item1"":0", serialized);
+            Assert.Contains(@"""Item2"":""2""", serialized);
+            Assert.Contains(@"""Item3"":3", serialized);
+            Assert.Contains(@"""Item4"":null", serialized);
+            Assert.Contains(@"""Item5"":null", serialized);
+            Assert.Contains(@"""Item6"":6", serialized);
+            Assert.Contains(@"""Item7"":{", serialized);
+
+            serialized = JsonSerializer.Serialize(obj.Item7);
+            Assert.Contains(@"""X"":0", serialized);
+            Assert.Contains(@"""Y"":0", serialized);
+            Assert.Contains(@"""Z"":0", serialized);
+
+            // Although no Json is provided for the 8th item, ArgumentException is still thrown as we use default(int) as the argument/
+            // System.ArgumentException : The last element of an eight element tuple must be a Tuple.
+            // We pass the number 8, not a new Tuple<int>(default(int)).
+            // Fixing this needs special casing. Newtonsoft behaves the same way.
+            Assert.Throws<ArgumentException>(() => Serializer.Deserialize<Tuple<int, string, int, string, string, int, Point_3D_Struct, int>>(input));
+        }
+
+        [Fact]
+        public void TupleDeserializationWorks_ClassWithParameterizedCtor()
+        {
+            string classJson = ClassWithConstructor_SimpleAndComplexParameters.s_json;
+
+            StringBuilder sb = new StringBuilder();
+            sb.Append("{");
+            for (int i = 0; i < 6; i++)
+            {
+                sb.Append(@$"""Item{i + 1}"":{classJson},");
+            }
+            sb.Append(@$"""Item7"":{classJson}");
+            sb.Append("}");
+
+            string complexTupleJson = sb.ToString();
+
+            var complexTuple = Serializer.Deserialize<Tuple<
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters>>(complexTupleJson);
+
+            complexTuple.Item1.Verify();
+            complexTuple.Item2.Verify();
+            complexTuple.Item3.Verify();
+            complexTuple.Item4.Verify();
+            complexTuple.Item5.Verify();
+            complexTuple.Item6.Verify();
+            complexTuple.Item7.Verify();
+        }
+
+        [Fact]
+        public void TupleDeserializationWorks_ClassWithParameterlessCtor()
+        {
+            string classJson = SimpleTestClass.s_json;
+
+            StringBuilder sb = new StringBuilder();
+            sb.Append("{");
+            for (int i = 0; i < 6; i++)
+            {
+                sb.Append(@$"""Item{i + 1}"":{classJson},");
+            }
+            sb.Append(@$"""Item7"":{classJson}");
+            sb.Append("}");
+
+            string complexTupleJson = sb.ToString();
+
+            var complexTuple = Serializer.Deserialize<Tuple<
+                SimpleTestClass,
+                SimpleTestClass,
+                SimpleTestClass,
+                SimpleTestClass,
+                SimpleTestClass,
+                SimpleTestClass,
+                SimpleTestClass>>(complexTupleJson);
+
+            complexTuple.Item1.Verify();
+            complexTuple.Item2.Verify();
+            complexTuple.Item3.Verify();
+            complexTuple.Item4.Verify();
+            complexTuple.Item5.Verify();
+            complexTuple.Item6.Verify();
+            complexTuple.Item7.Verify();
+        }
+
+        [Fact]
+        public void NoConstructorHandlingWhenObjectHasConverter()
+        {
+            // Baseline without converter
+            string serialized = JsonSerializer.Serialize(new Point_3D(10, 6));
+
+            Point_3D point = Serializer.Deserialize<Point_3D>(serialized);
+            Assert.Equal(10, point.X);
+            Assert.Equal(6, point.Y);
+            Assert.Equal(50, point.Z);
+
+            serialized = JsonSerializer.Serialize(new[] { new Point_3D(10, 6) });
+
+            point = Serializer.Deserialize<Point_3D[]>(serialized)[0];
+            Assert.Equal(10, point.X);
+            Assert.Equal(6, point.Y);
+            Assert.Equal(50, point.Z);
+
+            serialized = JsonSerializer.Serialize(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) });
+
+            point = Serializer.Deserialize<WrapperForPoint_3D>(serialized).Point_3D;
+            Assert.Equal(10, point.X);
+            Assert.Equal(6, point.Y);
+            Assert.Equal(50, point.Z);
+
+            // Converters for objects with parameterized ctors are honored
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new ConverterForPoint3D());
+
+            serialized = JsonSerializer.Serialize(new Point_3D(10, 6));
+
+            point = Serializer.Deserialize<Point_3D>(serialized, options);
+            Assert.Equal(4, point.X);
+            Assert.Equal(4, point.Y);
+            Assert.Equal(4, point.Z);
+
+            serialized = JsonSerializer.Serialize(new[] { new Point_3D(10, 6) });
+
+            point = Serializer.Deserialize<Point_3D[]>(serialized, options)[0];
+            Assert.Equal(4, point.X);
+            Assert.Equal(4, point.Y);
+            Assert.Equal(4, point.Z);
+
+            serialized = JsonSerializer.Serialize(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) });
+
+            point = Serializer.Deserialize<WrapperForPoint_3D>(serialized, options).Point_3D;
+            Assert.Equal(4, point.X);
+            Assert.Equal(4, point.Y);
+            Assert.Equal(4, point.Z);
+        }
+
+        [Fact]
+        public void ConstructorHandlingHonorsCustomConverters()
+        {
+            // Baseline, use internal converters for primitives
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""X"":2,""Y"":3}");
+            Assert.Equal(2, point.X);
+            Assert.Equal(3, point.Y);
+
+            // Honor custom converters
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new ConverterForInt32());
+
+            point = Serializer.Deserialize<Point_2D>(@"{""X"":2,""Y"":3}", options);
+            Assert.Equal(25, point.X);
+            Assert.Equal(25, point.X);
+        }
+
+        [Fact]
+        public void CanDeserialize_ObjectWith_Ctor_With_64_Params()
+        {
+            void RunTest<T>()
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.Append("{");
+                for (int i = 0; i < 63; i++)
+                {
+                    sb.Append($@"""Int{i}"":{i},");
+                }
+                sb.Append($@"""Int63"":63");
+                sb.Append("}");
+
+                string input = sb.ToString();
+
+                object obj = Serializer.Deserialize<T>(input);
+                for (int i = 0; i < 64; i++)
+                {
+                    Assert.Equal(i, (int)typeof(T).GetProperty($"Int{i}").GetValue(obj));
+                }
+            }
+
+            RunTest<Struct_With_Ctor_With_64_Params>();
+            RunTest<Class_With_Ctor_With_64_Params>();
+        }
+
+        [Fact]
+        public void Cannot_Deserialize_ObjectWith_Ctor_With_65_Params()
+        {
+            void RunTest<T>()
+            {
+                Type type = typeof(T);
+
+                StringBuilder sb = new StringBuilder();
+                sb.Append("{");
+                for (int i = 0; i < 64; i++)
+                {
+                    sb.Append($@"""Int{i}"":{i},");
+                }
+                sb.Append($@"""Int64"":64");
+                sb.Append("}");
+
+                string input = sb.ToString();
+
+                sb = new StringBuilder();
+                sb.Append("(");
+                for (int i = 0; i < 64; i++)
+                {
+                    sb.Append("Int32, ");
+                }
+                sb.Append("Int32");
+                sb.Append(")");
+
+                string ctorAsString = sb.ToString();
+
+                NotSupportedException ex = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<T>(input));
+                string strEx = ex.ToString();
+                Assert.Contains(ctorAsString, strEx);
+                Assert.Contains(type.ToString(), strEx);
+
+                ex = Assert.Throws<NotSupportedException>(() => Serializer.Deserialize<T>("{}"));
+                strEx = ex.ToString();
+                Assert.Contains(ctorAsString, strEx);
+                Assert.Contains(type.ToString(), strEx);
+            }
+
+            RunTest<Class_With_Ctor_With_65_Params>();
+            RunTest<Struct_With_Ctor_With_65_Params>();
+        }
+
+        [Fact]
+        public void Deserialize_ObjectWith_Ctor_With_65_Params_IfNull()
+        {
+            Assert.Null(Serializer.Deserialize<Class_With_Ctor_With_65_Params>("null"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Struct_With_Ctor_With_65_Params>("null"));
+        }
+
+        [Fact]
+        public void Escaped_ParameterNames_Work()
+        {
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""\u0058"":1,""\u0059"":2}");
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+        }
+
+        [Fact]
+        public void FirstParameterWins()
+        {
+            Point_2D point = Serializer.Deserialize<Point_2D>(@"{""X"":1,""Y"":2,""X"":4}");
+            Assert.Equal(4, point.X); // Not 1.
+            Assert.Equal(2, point.Y);
+        }
+
+        [Fact]
+        public void SubsequentParameter_GoesToExtensionData()
+        {
+            string json = @"{
+                ""FirstName"":""Jet"",
+                ""Id"":""270bb22b-4816-4bd9-9acd-8ec5b1a896d3"",
+                ""EmailAddress"":""jetdoe@outlook.com"",
+                ""Id"":""0b3aa420-2e98-47f7-8a49-fea233b89416"",
+                ""LastName"":""Doe"",
+                ""Id"":""63cf821d-fd47-4782-8345-576d9228a534""
+                }";
+
+            Parameterized_Person person = Serializer.Deserialize<Parameterized_Person>(json);
+            Assert.Equal("Jet", person.FirstName);
+            Assert.Equal("Doe", person.LastName);
+            Assert.Equal("63cf821d-fd47-4782-8345-576d9228a534", person.Id.ToString());
+            Assert.Equal("jetdoe@outlook.com", person.ExtensionData["EmailAddress"].GetString());
+            Assert.False(person.ExtensionData.ContainsKey("Id"));
+        }
+
+        [Fact]
+        public void BitVector32_UsesStructDefaultCtor_MultipleParameterizedCtor()
+        {
+            string serialized = JsonSerializer.Serialize(new BitVector32(1));
+            Assert.Equal(0, Serializer.Deserialize<BitVector32>(serialized).Data);
+        }
+
+        [Fact]
+        public void HonorExtensionDataGeneric()
+        {
+            var obj1 = Serializer.Deserialize<SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt>(@"{""key"": ""value""}");
+            Assert.Equal("value", obj1.ExtensionData["key"].GetString());
+
+            var obj2 = Serializer.Deserialize<SimpleClassWithParameterizedCtor_GenericDictionary_ObjectExt>(@"{""key"": ""value""}");
+            Assert.Equal("value", ((JsonElement)obj2.ExtensionData["key"]).GetString());
+
+            var obj3 = Serializer.Deserialize<SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_JsonElementExt>(@"{""key"": ""value""}");
+            Assert.Equal("value", obj3.ExtensionData["key"].GetString());
+
+            var obj4 = Serializer.Deserialize<SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_ObjectExt>(@"{""key"": ""value""}");
+            Assert.Equal("value", ((JsonElement)obj4.ExtensionData["key"]).GetString());
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_Honors_JsonPropertyName()
+        {
+            Point_MembersHave_JsonPropertyName point = new Point_MembersHave_JsonPropertyName(1, 2);
+
+            string json = JsonSerializer.Serialize(point);
+            Assert.Contains(@"""XValue"":1", json);
+            Assert.Contains(@"""YValue"":2", json);
+
+            point = Serializer.Deserialize<Point_MembersHave_JsonPropertyName>(json);
+            point.Verify();
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_Honors_JsonPropertyName_CaseInsensitiveWorks()
+        {
+            string json = @"{""XVALUE"":1,""yvalue"":2}";
+
+            // Without case insensitivity, there's no match.
+            Point_MembersHave_JsonPropertyName point = Serializer.Deserialize<Point_MembersHave_JsonPropertyName>(json);
+
+            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+            point = Serializer.Deserialize<Point_MembersHave_JsonPropertyName>(json, options);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_Honors_ConverterOnProperty()
+        {
+            var point = Serializer.Deserialize<Point_MembersHave_JsonConverter>(Point_MembersHave_JsonConverter.s_json);
+            point.Verify();
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_Honors_JsonIgnore()
+        {
+            var point = Serializer.Deserialize<Point_MembersHave_JsonIgnore>(Point_MembersHave_JsonIgnore.s_json);
+            point.Verify();
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_UseNamingPolicy_ToMatch()
+        {
+            var options = new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = new LowerCaseNamingPolicy()
+            };
+
+            string json = JsonSerializer.Serialize(new Point_ExtendedPropNames(1, 2), options);
+
+            // If we don't use naming policy, then we can't match serialized properties to constructor parameters on deserialization.
+            var point = Serializer.Deserialize<Point_ExtendedPropNames>(json);
+            Assert.Equal(0, point.XValue);
+            Assert.Equal(0, point.YValue);
+
+            point = Serializer.Deserialize<Point_ExtendedPropNames>(json, options);
+            Assert.Equal(1, point.XValue);
+            Assert.Equal(2, point.YValue);
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_UseNamingPolicy_ToMatch_CaseInsensitiveWorks()
+        {
+            var options1 = new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = new SimpleSnakeCasePolicy()
+            };
+
+            string json = @"{""x_VaLUE"":1,""Y_vALue"":2}";
+
+            // If we don't use case sensitivity, then we can't match serialized properties to constructor parameters on deserialization.
+            Point_ExtendedPropNames point = Serializer.Deserialize<Point_ExtendedPropNames>(json, options1);
+            Assert.Equal(0, point.XValue);
+            Assert.Equal(0, point.YValue);
+
+            var options2 = new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = new SimpleSnakeCasePolicy(),
+                PropertyNameCaseInsensitive = true,
+            };
+
+            point = Serializer.Deserialize<Point_ExtendedPropNames>(json, options2);
+            Assert.Equal(1, point.XValue);
+            Assert.Equal(2, point.YValue);
+        }
+
+        [Fact]
+        public void ArgumentDeserialization_UseNamingPolicy_InvalidPolicyFails()
+        {
+            var options = new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = new NullNamingPolicy()
+            };
+
+            Assert.Throws<InvalidOperationException>(() => Serializer.Deserialize<Point_ExtendedPropNames>("{}", options));
+        }
+
+        [Fact]
+        public void ComplexJson_As_LastCtorArg()
+        {
+            Point_With_Array obj1 = Serializer.Deserialize<Point_With_Array>(Point_With_Array.s_json);
+            ((ITestClass)obj1).Verify();
+
+            Point_With_Dictionary obj2 = Serializer.Deserialize<Point_With_Dictionary>(Point_With_Dictionary.s_json);
+            ((ITestClass)obj2).Verify();
+
+            Point_With_Object obj3 = Serializer.Deserialize<Point_With_Object>(Point_With_Object.s_json);
+            ((ITestClass)obj3).Verify();
+        }
+
+        [Fact]
+        public void NumerousPropertiesWork()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.Append("{");
+            sb.Append(@"""X"":1,");
+            sb.Append(@"""Y"":2,");
+
+            for (int i = 0; i < 65; i++)
+            {
+                sb.Append($@"""Z"":{i},");
+            }
+
+            sb.Append(@"""Z"":66");
+            sb.Append("}");
+
+            string json = sb.ToString();
+
+            var point = Serializer.Deserialize<Point_With_Property>(json);
+            Assert.Equal(1, point.X);
+            Assert.Equal(2, point.Y);
+            Assert.Equal(66, point.Z);
+        }
+
+        [Fact]
+        public void ArgumentStateNotOverwritten()
+        {
+            ClassWithNestedClass obj = new ClassWithNestedClass(myClass: null, myPoint: default);
+            ClassWithNestedClass obj1 = new ClassWithNestedClass(myClass: obj, myPoint: new Point_2D_Struct_WithAttribute(1, 2));
+            ClassWithNestedClass obj2 = new ClassWithNestedClass(myClass: obj1, myPoint: new Point_2D_Struct_WithAttribute(3, 4));
+
+            string json = JsonSerializer.Serialize(obj2);
+
+            obj2 = Serializer.Deserialize<ClassWithNestedClass>(json);
+            Assert.Equal(3, obj2.MyPoint.X);
+            Assert.Equal(4, obj2.MyPoint.Y);
+
+            obj1 = obj2.MyClass;
+            Assert.Equal(1, obj1.MyPoint.X);
+            Assert.Equal(2, obj1.MyPoint.Y);
+
+            obj = obj1.MyClass;
+            Assert.Equal(0, obj.MyPoint.X);
+            Assert.Equal(0, obj.MyPoint.Y);
+
+            Assert.Null(obj.MyClass);
+        }
+
+        [Fact]
+        public void FourArgsWork()
+        {
+            string json = JsonSerializer.Serialize(new StructWithFourArgs(1, 2, 3, 4));
+
+            var obj = Serializer.Deserialize<StructWithFourArgs>(json);
+            Assert.Equal(1, obj.W);
+            Assert.Equal(2, obj.X);
+            Assert.Equal(3, obj.Y);
+            Assert.Equal(4, obj.Z);
+        }
+
+        [Fact]
+        public void InvalidJsonFails()
+        {
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D>("{1"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D>("{x"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D>("{{"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D>("{true"));
+
+            // Also test deserialization of objects with parameterless ctors
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D_Struct>("{1"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D_Struct>("{x"));
+            Assert.Throws<JsonException>(() => Serializer.Deserialize<Point_2D_Struct>("{true"));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Stream.cs b/src/libraries/System.Text.Json/tests/Serialization/ConstructorTests.Stream.cs
new file mode 100644 (file)
index 0000000..6872cd5
--- /dev/null
@@ -0,0 +1,323 @@
+// 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.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract partial class ConstructorTests
+    {
+        [Fact]
+        public void ReadSimpleObjectAsync()
+        {
+            async Task RunTest<T>(byte[] testData)
+            {
+                using (MemoryStream stream = new MemoryStream(testData))
+                {
+                    JsonSerializerOptions options = new JsonSerializerOptions
+                    {
+                        DefaultBufferSize = 1
+                    };
+
+                    var obj = await JsonSerializer.DeserializeAsync<T>(stream, options);
+                    ((ITestClass)obj).Verify();
+                }
+            }
+
+            // Array size is the count of the following tests.
+            Task[] tasks = new Task[14];
+
+            // Simple models can be deserialized.
+            tasks[0] = Task.Run(async () => await RunTest<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_data));
+            // Complex models can be deserialized.
+            tasks[1] = Task.Run(async () => await RunTest<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_data));
+            tasks[2] = Task.Run(async () => await RunTest<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_data));
+            // JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
+            tasks[3] = Task.Run(async () => await RunTest<Person_Class>(Person_Class.s_data));
+            tasks[4] = Task.Run(async () => await RunTest<Person_Struct>(Person_Struct.s_data));
+            // JSON that doesn't bind to ctor args or properties are sent to ext data if avaiable.
+            tasks[5] = Task.Run(async () => await RunTest<Parameterized_Person>(Parameterized_Person.s_data));
+            tasks[6] = Task.Run(async () => await RunTest<Parameterized_Person_ObjExtData>(Parameterized_Person_ObjExtData.s_data));
+            // Up to 64 ctor args are supported.
+            tasks[7] = Task.Run(async () => await RunTest<Class_With_Ctor_With_64_Params>(Class_With_Ctor_With_64_Params.Data));
+            // Arg deserialization honors attributes on matching property.
+            tasks[8] = Task.Run(async () => await RunTest<Point_MembersHave_JsonPropertyName>(Point_MembersHave_JsonPropertyName.s_data));
+            tasks[9] = Task.Run(async () => await RunTest<Point_MembersHave_JsonConverter>(Point_MembersHave_JsonConverter.s_data));
+            tasks[10] = Task.Run(async () => await RunTest<Point_MembersHave_JsonIgnore>(Point_MembersHave_JsonIgnore.s_data));
+            // Complex JSON as last argument works
+            tasks[11] = Task.Run(async () => await RunTest<Point_With_Array>(Point_With_Array.s_data));
+            tasks[12] = Task.Run(async () => await RunTest<Point_With_Dictionary>(Point_With_Dictionary.s_data));
+            tasks[13] = Task.Run(async () => await RunTest<Point_With_Object>(Point_With_Object.s_data));
+        }
+
+        [Fact]
+        public void ReadSimpleObjectWithTrailingTriviaAsync()
+        {
+            async Task RunTest<T>(string testData)
+            {
+                byte[] data = Encoding.UTF8.GetBytes(testData + " /* Multi\r\nLine Comment */\t");
+                using (MemoryStream stream = new MemoryStream(data))
+                {
+                    JsonSerializerOptions options = new JsonSerializerOptions
+                    {
+                        DefaultBufferSize = 1,
+                        ReadCommentHandling = JsonCommentHandling.Skip,
+                    };
+
+                    var obj = await JsonSerializer.DeserializeAsync<T>(stream, options);
+                    ((ITestClass)obj).Verify();
+                }
+            }
+
+            // Array size is the count of the following tests.
+            Task[] tasks = new Task[14];
+
+            // Simple models can be deserialized.
+            tasks[0] = Task.Run(async () => await RunTest<Parameterized_IndexViewModel_Immutable>(Parameterized_IndexViewModel_Immutable.s_json));
+            // Complex models can be deserialized.
+            tasks[1] = Task.Run(async () => await RunTest<ClassWithConstructor_SimpleAndComplexParameters>(ClassWithConstructor_SimpleAndComplexParameters.s_json));
+            tasks[2] = Task.Run(async () => await RunTest<Parameterized_Class_With_ComplexTuple>(Parameterized_Class_With_ComplexTuple.s_json));
+            // JSON that doesn't bind to ctor args are matched with properties or ignored (as appropriate).
+            tasks[3] = Task.Run(async () => await RunTest<Person_Class>(Person_Class.s_json));
+            tasks[4] = Task.Run(async () => await RunTest<Person_Struct>(Person_Struct.s_json));
+            // JSON that doesn't bind to ctor args or properties are sent to ext data if avaiable.
+            tasks[5] = Task.Run(async () => await RunTest<Parameterized_Person>(Parameterized_Person.s_json));
+            tasks[6] = Task.Run(async () => await RunTest<Parameterized_Person_ObjExtData>(Parameterized_Person_ObjExtData.s_json));
+            // Up to 64 ctor args are supported.
+            tasks[7] = Task.Run(async () => await RunTest<Class_With_Ctor_With_64_Params>(Encoding.UTF8.GetString(Class_With_Ctor_With_64_Params.Data)));
+            // Arg8deserialization honors attributes on matching property.
+            tasks[8] = Task.Run(async () => await RunTest<Point_MembersHave_JsonPropertyName>(Point_MembersHave_JsonPropertyName.s_json));
+            tasks[9] = Task.Run(async () => await RunTest<Point_MembersHave_JsonConverter>(Point_MembersHave_JsonConverter.s_json));
+            tasks[10] = Task.Run(async () => await RunTest<Point_MembersHave_JsonIgnore>(Point_MembersHave_JsonIgnore.s_json));
+            // Complex JSON as last argument works
+            tasks[11] = Task.Run(async () => await RunTest<Point_With_Array>(Point_With_Array.s_json));
+            tasks[12] = Task.Run(async () => await RunTest<Point_With_Dictionary>(Point_With_Dictionary.s_json));
+            tasks[13] = Task.Run(async () => await RunTest<Point_With_Object>(Point_With_Object.s_json));
+
+            Task.WaitAll(tasks);
+        }
+
+        [Fact]
+        public void Cannot_DeserializeAsync_ObjectWith_Ctor_With_65_Params()
+        {
+            async Task RunTest<T>()
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.Append("{");
+                for (int i = 0; i < 64; i++)
+                {
+                    sb.Append($@"""Int{i}"":{i},");
+                }
+                sb.Append($@"""Int64"":64");
+                sb.Append("}");
+
+                string input = sb.ToString();
+
+                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(input)))
+                {
+                    JsonSerializerOptions options = new JsonSerializerOptions
+                    {
+                        DefaultBufferSize = 1
+                    };
+
+                    await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializer.DeserializeAsync<T>(stream, options));
+                }
+
+                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("{}")))
+                {
+                    JsonSerializerOptions options = new JsonSerializerOptions
+                    {
+                        DefaultBufferSize = 1
+                    };
+
+                    await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializer.DeserializeAsync<T>(stream, options));
+                }
+            }
+
+            Task[] tasks = new Task[2];
+
+            tasks[0] = Task.Run(async () => await RunTest<Class_With_Ctor_With_65_Params>());
+            tasks[1] = Task.Run(async () => await RunTest<Struct_With_Ctor_With_65_Params>());
+
+            Task.WaitAll(tasks);
+        }
+
+        [Fact]
+        public async Task ExerciseStreamCodePaths()
+        {
+            static string GetPropertyName(int index) =>
+                new string(new char[] { Convert.ToChar(index + 65), 'V', 'a', 'l', 'u', 'e' });
+
+            static byte[] GeneratePayload(int i, string value)
+            {
+                string whiteSpace = new string(' ', 16 );
+
+                StringBuilder sb;
+
+                string prefix = "";
+
+                sb = new StringBuilder();
+                sb.Append("{");
+
+                for (int j = 0; j < i; j++)
+                {
+                    sb.Append(prefix);
+                    sb.Append($@"""{GetPropertyName(j)}"":""{value}""");
+                    prefix = ",";
+                }
+
+                sb.Append(prefix);
+                sb.Append($@"{whiteSpace}""{GetPropertyName(i)}"":{whiteSpace}""{value}""");
+                prefix = ",";
+
+                for (int j = 0; j < 10; j++)
+                {
+                    sb.Append(prefix);
+                    string keyPair = $@"""rand"":[""{value}""]";
+                    sb.Append($@"""Value{j}"":{{{keyPair},{keyPair},{keyPair}}}");
+                }
+
+                for (int j = i + 1; j < 20; j++)
+                {
+                    sb.Append(prefix);
+                    sb.Append($@"""{GetPropertyName(j)}"":""{value}""");
+                }
+
+                sb.Append("}");
+
+                return Encoding.UTF8.GetBytes(sb.ToString());
+            }
+
+            const string value = "ul4Oolt4VgbNm5Y1qPX911wxhyHFEQmmWBcIBR6BfUaNuIn3YOJ8vqtqz2WAh924rEILMzlh6JUhQDcmH00SI6Kv4iGTHQfGXxqWul4Oolt4VgbNm5Y1qPX911wxhyHFEQmmWBcIBR6";
+
+            for (int i = 0; i < 20; i++)
+            {
+                using (MemoryStream stream = new MemoryStream(GeneratePayload(i, value)))
+                {
+                    JsonSerializerOptions options = new JsonSerializerOptions
+                    {
+                        DefaultBufferSize = 1
+                    };
+
+                    ClassWithStrings obj = await JsonSerializer.DeserializeAsync<ClassWithStrings>(stream, options);
+                    obj.Verify(value);
+                }
+            }
+        }
+
+        public class ClassWithStrings
+        {
+            // Ctor args.
+
+            // Ignored.
+
+            [JsonIgnore]
+            public string AValue { get; }
+            [JsonIgnore]
+            public string EValue { get; }
+            [JsonIgnore]
+            public string IValue { get; }
+            [JsonIgnore]
+            public string MValue { get; }
+            [JsonIgnore]
+            public string QValue { get; }
+
+            // Populated.
+
+            public string CValue { get; }
+            public string GValue { get; }
+            public string KValue { get; }
+            public string OValue { get; }
+            public string SValue { get; }
+
+            // Properties.
+
+            // Ignored - no setter.
+
+            public string BValue { get; }
+            public string FValue { get; }
+            public string JValue { get; }
+            public string NValue { get; }
+            public string RValue { get; }
+
+            // Populated.
+
+            public string DValue { get; set; }
+            public string HValue { get; set; }
+            public string LValue { get; set; }
+            public string PValue { get; set; }
+            public string TValue { get; set; }
+
+            [JsonExtensionData]
+            public Dictionary<string, JsonElement> ExtensionData { get; set; }
+
+            public ClassWithStrings(
+                string aValue,
+                string cValue,
+                string eValue,
+                string gValue,
+                string iValue,
+                string kValue,
+                string mValue,
+                string oValue,
+                string qValue,
+                string sValue)
+            {
+                AValue = aValue;
+                CValue = cValue;
+                EValue = eValue;
+                GValue = gValue;
+                IValue = iValue;
+                KValue = kValue;
+                MValue = mValue;
+                OValue = oValue;
+                QValue = qValue;
+                SValue = sValue;
+            }
+
+            public void Verify(string expectedStr)
+            {
+                // Ctor args.
+
+                // Ignored
+                Assert.Null(AValue);
+                Assert.Null(EValue);
+                Assert.Null(IValue);
+                Assert.Null(MValue);
+                Assert.Null(QValue);
+
+                Assert.Equal(expectedStr, CValue);
+                Assert.Equal(expectedStr, GValue);
+                Assert.Equal(expectedStr, KValue);
+                Assert.Equal(expectedStr, OValue);
+                Assert.Equal(expectedStr, SValue);
+
+                // Getter only members - skipped.
+                Assert.Null(BValue);
+                Assert.Null(FValue);
+                Assert.Null(JValue);
+                Assert.Null(NValue);
+                Assert.Null(RValue);
+
+                // Members with setters
+                Assert.Equal(expectedStr, DValue);
+                Assert.Equal(expectedStr, HValue);
+                Assert.Equal(expectedStr, LValue);
+                Assert.Equal(expectedStr, PValue);
+                Assert.Equal(expectedStr, TValue);
+
+                Assert.Equal(10, ExtensionData.Count);
+
+                foreach (JsonElement value in ExtensionData.Values)
+                {
+                    string keyPair = $@"""rand"":[""{expectedStr}""]";
+                    Assert.Equal($@"{{{keyPair},{keyPair},{keyPair}}}", value.GetRawText());
+                }
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs b/src/libraries/System.Text.Json/tests/Serialization/DeserializationWrapper.cs
new file mode 100644 (file)
index 0000000..77eae2f
--- /dev/null
@@ -0,0 +1,49 @@
+// 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.IO;
+using System.Threading.Tasks;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    /// <summary>
+    /// Base class for wrapping serialization calls which allows tests to run under different configurations.
+    /// </summary>
+    public abstract class DeserializationWrapper
+    {
+        private static readonly JsonSerializerOptions _optionsWithSmallBuffer = new JsonSerializerOptions { DefaultBufferSize = 1 };
+
+        public static DeserializationWrapper StringTValueSerializer => new StringTValueSerializerWrapper();
+        public static DeserializationWrapper StreamTValueSerializer => new StreamTValueSerializerWrapper();
+
+        protected internal abstract T Deserialize<T>(string json, JsonSerializerOptions options = null);
+
+        private class StringTValueSerializerWrapper : DeserializationWrapper
+        {
+            protected internal override T Deserialize<T>(string json, JsonSerializerOptions options = null)
+            {
+                return JsonSerializer.Deserialize<T>(json, options);
+            }
+        }
+
+        private class StreamTValueSerializerWrapper : DeserializationWrapper
+        {
+            protected internal override T Deserialize<T>(string json, JsonSerializerOptions options = null)
+            {
+                if (options == null)
+                {
+                    options = _optionsWithSmallBuffer;
+                }
+
+                return Task.Run(async () =>
+                {
+                    using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+                    {
+                        return await JsonSerializer.DeserializeAsync<T>(stream, options);
+                    }
+                }).GetAwaiter().GetResult();
+            }
+        }
+    }
+}
index 7d776b9..0613262 100644 (file)
@@ -2173,7 +2173,6 @@ namespace System.Text.Json.Serialization.Tests
         [Fact]
         public static void DictionaryWith_ObjectWithNoParameterlessCtor_AsValue_Throws()
         {
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<string, ClassWithoutParameterlessCtor>>(@"{""key"":{}}"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<string, ClassWithInternalParameterlessConstructor>>(@"{""key"":{}}"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<Dictionary<string, ClassWithPrivateParameterlessConstructor>>(@"{""key"":{}}"));
         }
index 2dde909..6787ab0 100644 (file)
@@ -217,7 +217,6 @@ namespace System.Text.Json.Serialization.Tests
         [Fact]
         public static void ReadObjectFail_ReferenceTypeMissingPublicParameterlessConstructor()
         {
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<PublicParameterizedConstructorTestClass>(@"{""Name"":""Name!""}"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithInternalParameterlessCtor>(@"{""Name"":""Name!""}"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithPrivateParameterlessCtor>(@"{""Name"":""Name!""}"));
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<CollectionWithoutPublicParameterlessCtor>(@"[""foo"", 1, false]"));
@@ -228,10 +227,7 @@ namespace System.Text.Json.Serialization.Tests
 
         private class PublicParameterizedConstructorTestClass
         {
-            public PublicParameterizedConstructorTestClass(string name)
-            {
-                Debug.Fail("The JsonSerializer should not be callin non-public ctors, by default.");
-            }
+            public PublicParameterizedConstructorTestClass(string name) { }
 
             private PublicParameterizedConstructorTestClass(int internalId)
             {
diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Constructor.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Constructor.cs
new file mode 100644 (file)
index 0000000..c0be977
--- /dev/null
@@ -0,0 +1,2274 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public class PrivateParameterlessCtor
+    {
+        private PrivateParameterlessCtor() { }
+    }
+
+    public class PrivateParameterizedCtor
+    {
+        public int X { get; }
+
+        private PrivateParameterizedCtor(int x) { }
+    }
+
+    public class PrivateParameterizedCtor_WithAttribute
+    {
+        public int X { get; }
+
+        [JsonConstructor]
+        private PrivateParameterizedCtor_WithAttribute(int x) => X = x;
+    }
+
+    public class InternalParameterlessCtor
+    {
+        internal InternalParameterlessCtor() { }
+    }
+
+    public class InternalParameterizedCtor
+    {
+        public int X { get; }
+
+        internal InternalParameterizedCtor(int x) { }
+    }
+
+    public class InternalParameterizedCtor_WithAttribute
+    {
+        public int X { get; }
+
+        [JsonConstructor]
+        internal InternalParameterizedCtor_WithAttribute(int x) => X = x;
+    }
+
+    public class ProtectedParameterlessCtor
+    {
+        protected ProtectedParameterlessCtor() { }
+    }
+
+    public class ProtectedParameterizedCtor
+    {
+        public int X { get; }
+
+        protected ProtectedParameterizedCtor(int x) { }
+    }
+
+    public class ProtectedParameterizedCtor_WithAttribute
+    {
+        public int X { get; }
+
+        [JsonConstructor]
+        protected ProtectedParameterizedCtor_WithAttribute(int x) => X = x;
+    }
+
+    public class PrivateParameterlessCtor_InternalParameterizedCtor_WithMultipleAttributes
+    {
+        [JsonConstructor]
+        private PrivateParameterlessCtor_InternalParameterizedCtor_WithMultipleAttributes() { }
+
+        [JsonConstructor]
+        internal PrivateParameterlessCtor_InternalParameterizedCtor_WithMultipleAttributes(int value) { }
+    }
+
+    public class ProtectedParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes
+    {
+        [JsonConstructor]
+        protected ProtectedParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes() { }
+
+        [JsonConstructor]
+        private ProtectedParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes(int value) { }
+    }
+
+    public class PublicParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes
+    {
+        [JsonConstructor]
+        public PublicParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes() { }
+
+        [JsonConstructor]
+        private PublicParameterlessCtor_PrivateParameterizedCtor_WithMultipleAttributes(int value) { }
+    }
+
+    public class PublicParameterizedCtor_PublicParameterizedCtor_WithMultipleAttributes
+    {
+        [JsonConstructor]
+        public PublicParameterizedCtor_PublicParameterizedCtor_WithMultipleAttributes(int value) { }
+
+        [JsonConstructor]
+        public PublicParameterizedCtor_PublicParameterizedCtor_WithMultipleAttributes(float value) { }
+    }
+
+    public struct Struct_PublicParameterizedCtor_PrivateParameterizedCtor_WithMultipleAttributes
+    {
+        [JsonConstructor]
+        public Struct_PublicParameterizedCtor_PrivateParameterizedCtor_WithMultipleAttributes(float value) { }
+
+        [JsonConstructor]
+        private Struct_PublicParameterizedCtor_PrivateParameterizedCtor_WithMultipleAttributes(int value) { }
+    }
+
+    public struct Point_2D_Struct
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        public Point_2D_Struct(int x, int y) => (X, Y) = (x, y);
+    }
+
+    public struct Point_2D_Struct_WithAttribute
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D_Struct_WithAttribute(int x, int y) => (X, Y) = (x, y);
+    }
+
+    public struct Point_2D_Struct_WithMultipleAttributes
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D_Struct_WithMultipleAttributes(int x) => (X, Y) = (x, 0);
+
+        [JsonConstructor]
+        public Point_2D_Struct_WithMultipleAttributes(int x, int y) => (X, Y) = (x, y);
+    }
+
+    public struct Point_2D_Struct_WithMultipleAttributes_OneNonPublic
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D_Struct_WithMultipleAttributes_OneNonPublic(int x) => (X, Y) = (x, 0);
+
+        [JsonConstructor]
+        private Point_2D_Struct_WithMultipleAttributes_OneNonPublic(int x, int y) => (X, Y) = (x, y);
+    }
+
+    public class SinglePublicParameterizedCtor
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public SinglePublicParameterizedCtor() { }
+
+        public SinglePublicParameterizedCtor(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class SingleParameterlessCtor_MultiplePublicParameterizedCtor
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public SingleParameterlessCtor_MultiplePublicParameterizedCtor() { }
+
+        public SingleParameterlessCtor_MultiplePublicParameterizedCtor(int myInt)
+        {
+            MyInt = myInt;
+        }
+
+        public SingleParameterlessCtor_MultiplePublicParameterizedCtor(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public struct SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct(int myInt)
+        {
+            MyInt = myInt;
+            MyString = null;
+        }
+
+        public SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class PublicParameterizedCtor
+    {
+        public int MyInt { get; private set; }
+
+        public PublicParameterizedCtor(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public struct Struct_PublicParameterizedConstructor
+    {
+        public int MyInt { get; }
+
+        public Struct_PublicParameterizedConstructor(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public class PrivateParameterlessConstructor_PublicParameterizedCtor
+    {
+        public int MyInt { get; private set; }
+
+        private PrivateParameterlessConstructor_PublicParameterizedCtor() { }
+
+        public PrivateParameterlessConstructor_PublicParameterizedCtor(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public class PublicParameterizedCtor_WithAttribute
+    {
+        public int MyInt { get; private set; }
+
+        [JsonConstructor]
+        public PublicParameterizedCtor_WithAttribute(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public struct Struct_PublicParameterizedConstructor_WithAttribute
+    {
+        public int MyInt { get; }
+
+        [JsonConstructor]
+        public Struct_PublicParameterizedConstructor_WithAttribute(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public class PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute
+    {
+        public int MyInt { get; private set; }
+
+        private PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute() { }
+
+        [JsonConstructor]
+        public PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute(int myInt)
+        {
+            MyInt = myInt;
+        }
+    }
+
+    public class MultiplePublicParameterizedCtor
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public MultiplePublicParameterizedCtor(int myInt)
+        {
+            MyInt = myInt;
+        }
+
+        public MultiplePublicParameterizedCtor(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public struct MultiplePublicParameterizedCtor_Struct
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public MultiplePublicParameterizedCtor_Struct(int myInt)
+        {
+            MyInt = myInt;
+            MyString = null;
+        }
+
+        public MultiplePublicParameterizedCtor_Struct(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class MultiplePublicParameterizedCtor_WithAttribute
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        [JsonConstructor]
+        public MultiplePublicParameterizedCtor_WithAttribute(int myInt)
+        {
+            MyInt = myInt;
+        }
+
+        public MultiplePublicParameterizedCtor_WithAttribute(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public struct MultiplePublicParameterizedCtor_WithAttribute_Struct
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public MultiplePublicParameterizedCtor_WithAttribute_Struct(int myInt)
+        {
+            MyInt = myInt;
+            MyString = null;
+        }
+
+        [JsonConstructor]
+        public MultiplePublicParameterizedCtor_WithAttribute_Struct(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        public ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute() { }
+
+        [JsonConstructor]
+        public ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute(int myInt)
+        {
+            MyInt = myInt;
+        }
+
+        public ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class MultiplePublicParameterizedCtor_WithMultipleAttributes
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        [JsonConstructor]
+        public MultiplePublicParameterizedCtor_WithMultipleAttributes(int myInt)
+        {
+            MyInt = myInt;
+        }
+
+        [JsonConstructor]
+        public MultiplePublicParameterizedCtor_WithMultipleAttributes(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class PublicParameterlessConstructor_PublicParameterizedCtor_WithMultipleAttributes
+    {
+        public int MyInt { get; private set; }
+        public string MyString { get; private set; }
+
+        [JsonConstructor]
+        public PublicParameterlessConstructor_PublicParameterizedCtor_WithMultipleAttributes() { }
+
+        [JsonConstructor]
+        public PublicParameterlessConstructor_PublicParameterizedCtor_WithMultipleAttributes(int myInt, string myString)
+        {
+            MyInt = myInt;
+            MyString = myString;
+        }
+    }
+
+    public class Parameterized_StackWrapper : Stack
+    {
+        [JsonConstructor]
+        public Parameterized_StackWrapper(object[] elements)
+        {
+            foreach (object element in elements)
+            {
+                Push(element);
+            }
+        }
+    }
+
+    public class Parameterized_WrapperForICollection : ICollection
+    {
+        private List<object> _list = new List<object>();
+
+        public Parameterized_WrapperForICollection(object[] elements)
+        {
+            _list.AddRange(elements);
+        }
+
+        public int Count => ((ICollection)_list).Count;
+
+        public bool IsSynchronized => ((ICollection)_list).IsSynchronized;
+
+        public object SyncRoot => ((ICollection)_list).SyncRoot;
+
+        public void CopyTo(Array array, int index)
+        {
+            ((ICollection)_list).CopyTo(array, index);
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return ((ICollection)_list).GetEnumerator();
+        }
+    }
+
+    public class Point_2D : ITestClass
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D(int x, int y) => (X, Y) = (x, y);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+        }
+    }
+
+    public class Point_3D : ITestClass
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        public int Z { get; }
+
+        [JsonConstructor]
+        public Point_3D(int x, int y, int z = 50) => (X, Y, Z) = (x, y, z);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+            Assert.Equal(3, Z);
+        }
+    }
+
+    public struct Point_2D_With_ExtData : ITestClass
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D_With_ExtData(int x, int y)
+        {
+            X = x;
+            Y = y;
+            ExtensionData = new Dictionary<string, JsonElement>();
+        }
+
+        [JsonExtensionData]
+        public Dictionary<string, JsonElement> ExtensionData { get; set; }
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+        }
+    }
+
+    public struct WrapperForPoint_3D
+    {
+        public Point_3D Point_3D { get; set; }
+    }
+
+    public class ClassWrapperForPoint_3D
+    {
+        public Point_3D Point3D { get; }
+
+        public ClassWrapperForPoint_3D(Point_3D point3D)
+        {
+            Point3D = point3D;
+        }
+    }
+
+    public class ClassWrapper_For_Int_String
+    {
+        public int Int { get; }
+
+        public string String { get; }
+
+        public ClassWrapper_For_Int_String(int @int, string @string) // Parameter names are "int" and "string"
+        {
+            Int = @int;
+            String = @string;
+        }
+    }
+
+    public class ClassWrapper_For_Int_Point_3D_String
+    {
+        public int MyInt { get; }
+
+        public Point_3D_Struct MyPoint3DStruct { get; }
+
+        public string MyString { get; }
+
+        public ClassWrapper_For_Int_Point_3D_String(Point_3D_Struct myPoint3DStruct)
+        {
+            MyInt = 0;
+            MyPoint3DStruct = myPoint3DStruct;
+            MyString = null;
+        }
+
+        [JsonConstructor]
+        public ClassWrapper_For_Int_Point_3D_String(int myInt, Point_3D_Struct myPoint3DStruct, string myString)
+        {
+            MyInt = myInt;
+            MyPoint3DStruct = myPoint3DStruct;
+            MyString = myString;
+        }
+    }
+
+    public struct Point_3D_Struct
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        public int Z { get; }
+
+        [JsonConstructor]
+        public Point_3D_Struct(int x, int y, int z = 50) => (X, Y, Z) = (x, y, z);
+    }
+
+    public class Person_Class : ITestClass
+    {
+        public string FirstName { get; set; }
+        public string LastName { get; set; }
+        public string EmailAddress { get; }
+        public Guid Id { get; }
+        public int Age { get; }
+
+        public Point_2D Point2D { get; set; }
+        public Point_2D ReadOnlyPoint2D { get; }
+
+        public Point_2D_With_ExtData_Class Point2DWithExtDataClass { get; set; }
+        public Point_2D_With_ExtData_Class ReadOnlyPoint2DWithExtDataClass { get; }
+
+        public Point_3D_Struct Point3DStruct { get; set; }
+        public Point_3D_Struct ReadOnlyPoint3DStruct { get; }
+
+        public Point_2D_With_ExtData Point2DWithExtData { get; set; }
+        public Point_2D_With_ExtData ReadOnlyPoint2DWithExtData { get; }
+
+        // Test that objects deserialized with parameterless still work fine as properties
+        public SinglePublicParameterizedCtor SinglePublicParameterizedCtor { get; set; }
+        public SinglePublicParameterizedCtor ReadOnlySinglePublicParameterizedCtor { get; }
+
+        public Person_Class(
+            string emailAddress,
+            Guid id,
+            int age,
+            Point_2D readOnlyPoint2D,
+            Point_2D_With_ExtData_Class readOnlyPoint2DWithExtDataClass,
+            Point_3D_Struct readOnlyPoint3DStruct,
+            Point_2D_With_ExtData readOnlyPoint2DWithExtData,
+            SinglePublicParameterizedCtor readOnlySinglePublicParameterizedCtor)
+        {
+            EmailAddress = emailAddress;
+            Id = id;
+            Age = age;
+            ReadOnlyPoint2D = readOnlyPoint2D;
+            ReadOnlyPoint2DWithExtDataClass = readOnlyPoint2DWithExtDataClass;
+            ReadOnlyPoint3DStruct = readOnlyPoint3DStruct;
+            ReadOnlyPoint2DWithExtData = readOnlyPoint2DWithExtData;
+            ReadOnlySinglePublicParameterizedCtor = readOnlySinglePublicParameterizedCtor;
+        }
+
+        public static readonly string s_json =
+             @"{
+                ""FirstName"":""John"",
+                ""LastName"":""Doe"",
+                ""EmailAddress"":""johndoe@live.com"",
+                ""Id"":""f2c92fcc-459f-4287-90b6-a7cbd82aeb0e"",
+                ""Age"":24,
+                ""Point2D"":{""X"":1,""Y"":2},
+                ""ReadOnlyPoint2D"":{""X"":1,""Y"":2},
+                ""Point2DWithExtDataClass"":{""X"":1,""Y"":2,""b"":3},
+                ""ReadOnlyPoint2DWithExtDataClass"":{""X"":1,""Y"":2,""b"":3},
+                ""Point3DStruct"":{""X"":1,""Y"":2,""Z"":3},
+                ""ReadOnlyPoint3DStruct"":{""X"":1,""Y"":2,""Z"":3},
+                ""Point2DWithExtData"":{""X"":1,""Y"":2,""b"":3},
+                ""ReadOnlyPoint2DWithExtData"":{""X"":1,""Y"":2,""b"":3}
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal("John", FirstName);
+            Assert.Equal("Doe", LastName);
+            Assert.Equal("johndoe@live.com", EmailAddress);
+            Assert.Equal("f2c92fcc-459f-4287-90b6-a7cbd82aeb0e", Id.ToString());
+            Assert.Equal(24, Age);
+
+            string serialized = JsonSerializer.Serialize(this);
+            Assert.Contains(@"""Point2D"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2D"":{", serialized);
+            Assert.Contains(@"""Point2DWithExtDataClass"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2DWithExtDataClass"":{", serialized);
+            Assert.Contains(@"""Point3DStruct"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint3DStruct"":{", serialized);
+            Assert.Contains(@"""Point2DWithExtData"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2DWithExtData"":{", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2D);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2D);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2DWithExtDataClass);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2DWithExtDataClass);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(Point3DStruct);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""Z"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint3DStruct);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""Z"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2DWithExtData);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2DWithExtData);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+        }
+    }
+
+    public struct Person_Struct : ITestClass
+    {
+        public string FirstName { get; set; }
+        public string LastName { get; set; }
+        public string EmailAddress { get; }
+        public Guid Id { get; }
+        public int Age { get; }
+
+        public Point_2D Point2D { get; set; }
+        public Point_2D ReadOnlyPoint2D { get; }
+
+        public Point_2D_With_ExtData_Class Point2DWithExtDataClass { get; set; }
+        public Point_2D_With_ExtData_Class ReadOnlyPoint2DWithExtDataClass { get; }
+
+        public Point_3D_Struct Point3DStruct { get; set; }
+        public Point_3D_Struct ReadOnlyPoint3DStruct { get; }
+
+        public Point_2D_With_ExtData Point2DWithExtData { get; set; }
+        public Point_2D_With_ExtData ReadOnlyPoint2DWithExtData { get; }
+
+        // Test that objects deserialized with parameterless still work fine as properties
+        public SinglePublicParameterizedCtor SinglePublicParameterizedCtor { get; set; }
+        public SinglePublicParameterizedCtor ReadOnlySinglePublicParameterizedCtor { get; }
+
+        [JsonConstructor]
+        public Person_Struct(
+            string emailAddress,
+            Guid id,
+            int age,
+            Point_2D readOnlyPoint2D,
+            Point_2D_With_ExtData_Class readOnlyPoint2DWithExtDataClass,
+            Point_3D_Struct readOnlyPoint3DStruct,
+            Point_2D_With_ExtData readOnlyPoint2DWithExtData,
+            SinglePublicParameterizedCtor readOnlySinglePublicParameterizedCtor)
+        {
+            // Readonly, setting in ctor.
+            EmailAddress = emailAddress;
+            Id = id;
+            Age = age;
+            ReadOnlyPoint2D = readOnlyPoint2D;
+            ReadOnlyPoint2DWithExtDataClass = readOnlyPoint2DWithExtDataClass;
+            ReadOnlyPoint3DStruct = readOnlyPoint3DStruct;
+            ReadOnlyPoint2DWithExtData = readOnlyPoint2DWithExtData;
+            ReadOnlySinglePublicParameterizedCtor = readOnlySinglePublicParameterizedCtor;
+
+            // These properties will be set by serializer.
+            FirstName = null;
+            LastName = null;
+            Point2D = null;
+            Point2DWithExtDataClass = null;
+            Point3DStruct = default;
+            Point2DWithExtData = default;
+            SinglePublicParameterizedCtor = default;
+        }
+
+        public static readonly string s_json =
+             @"{
+                ""FirstName"":""John"",
+                ""LastName"":""Doe"",
+                ""EmailAddress"":""johndoe@live.com"",
+                ""Id"":""f2c92fcc-459f-4287-90b6-a7cbd82aeb0e"",
+                ""Age"":24,
+                ""Point2D"":{""X"":1,""Y"":2},
+                ""Junk"":""Data"",
+                ""ReadOnlyPoint2D"":{""X"":1,""Y"":2},
+                ""Point2DWithExtDataClass"":{""X"":1,""Y"":2,""b"":3},
+                ""ReadOnlyPoint2DWithExtDataClass"":{""X"":1,""Y"":2,""b"":3},
+                ""Point3DStruct"":{""X"":1,""Y"":2,""Z"":3},
+                ""More"":""Junk"",
+                ""ReadOnlyPoint3DStruct"":{""X"":1,""Y"":2,""Z"":3},
+                ""Point2DWithExtData"":{""X"":1,""Y"":2,""b"":3},
+                ""ReadOnlyPoint2DWithExtData"":{""X"":1,""Y"":2,""b"":3}
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal("John", FirstName);
+            Assert.Equal("Doe", LastName);
+            Assert.Equal("johndoe@live.com", EmailAddress);
+            Assert.Equal("f2c92fcc-459f-4287-90b6-a7cbd82aeb0e", Id.ToString());
+            Assert.Equal(24, Age);
+
+            string serialized = JsonSerializer.Serialize(this);
+            Assert.Contains(@"""Point2D"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2D"":{", serialized);
+            Assert.Contains(@"""Point2DWithExtDataClass"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2DWithExtDataClass"":{", serialized);
+            Assert.Contains(@"""Point3DStruct"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint3DStruct"":{", serialized);
+            Assert.Contains(@"""Point2DWithExtData"":{", serialized);
+            Assert.Contains(@"""ReadOnlyPoint2DWithExtData"":{", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2D);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2D);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2DWithExtDataClass);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2DWithExtDataClass);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(Point3DStruct);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""Z"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint3DStruct);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""Z"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(Point2DWithExtData);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+
+            serialized = JsonSerializer.Serialize(ReadOnlyPoint2DWithExtData);
+            Assert.Contains(@"""X"":1", serialized);
+            Assert.Contains(@"""Y"":2", serialized);
+            Assert.Contains(@"""b"":3", serialized);
+        }
+    }
+
+
+
+    public class Point_2D_With_ExtData_Class
+    {
+        public int X { get; }
+
+        public int Y { get; }
+
+        [JsonConstructor]
+        public Point_2D_With_ExtData_Class(int x, int y)
+        {
+            X = x;
+            Y = y;
+        }
+
+        [JsonExtensionData]
+        public Dictionary<string, JsonElement> ExtensionData { get; set; }
+    }
+
+    public class Point_CtorsIgnoreJson
+    {
+        public int X { get; set; }
+
+        public int Y { get; set; }
+
+        [JsonConstructor]
+        public Point_CtorsIgnoreJson(int x, int y)
+        {
+            X = 40;
+            Y = 60;
+        }
+    }
+
+    public class PointPropertyNamingPolicy : JsonNamingPolicy
+    {
+        public override string ConvertName(string name)
+        {
+            if (name == "X")
+            {
+                return "A";
+            }
+            else if (name == "Y")
+            {
+                return "B";
+            }
+
+            return name;
+        }
+    }
+
+    public class NullArgTester
+    {
+        public Point_3D_Struct Point3DStruct { get; }
+        public ImmutableArray<int> ImmutableArray { get; }
+        public int Int { get; }
+
+        public NullArgTester(Point_3D_Struct point3DStruct, ImmutableArray<int> immutableArray, int @int = 50)
+        {
+            Point3DStruct = point3DStruct;
+            ImmutableArray = immutableArray;
+            Int = @int;
+        }
+    }
+
+    public class ClassWithConstructor_SimpleAndComplexParameters : ITestClass
+    {
+        public byte MyByte { get; }
+        public sbyte MySByte { get; set; }
+        public char MyChar { get; }
+        public string MyString { get; }
+        public decimal MyDecimal { get; }
+        public bool MyBooleanTrue { get; set; }
+        public bool MyBooleanFalse { get; }
+        public float MySingle { get; set; }
+        public double MyDouble { get; }
+        public DateTime MyDateTime { get; set; }
+        public DateTimeOffset MyDateTimeOffset { get; }
+        public Guid MyGuid { get; }
+        public Uri MyUri { get; set; }
+        public SampleEnum MyEnum { get; }
+        public SampleEnumInt64 MyInt64Enum { get; }
+        public SampleEnumUInt64 MyUInt64Enum { get; }
+        public SimpleStruct MySimpleStruct { get; }
+        public SimpleTestStruct MySimpleTestStruct { get; set; }
+        public int[][][] MyInt16ThreeDimensionArray { get; }
+        public List<List<List<int>>> MyInt16ThreeDimensionList { get; }
+        public List<string> MyStringList { get; }
+        public IEnumerable MyStringIEnumerable { get; set; }
+        public IList MyStringIList { get; }
+        public ICollection MyStringICollection { get; set; }
+        public IEnumerable<string> MyStringIEnumerableT { get; }
+        public IReadOnlyList<string> MyStringIReadOnlyListT { get; }
+        public ISet<string> MyStringISetT { get; set; }
+        public KeyValuePair<string, string> MyStringToStringKeyValuePair { get; }
+        public IDictionary MyStringToStringIDict { get; set; }
+        public Dictionary<string, string> MyStringToStringGenericDict { get; }
+        public IDictionary<string, string> MyStringToStringGenericIDict { get; set; }
+        public IImmutableDictionary<string, string> MyStringToStringIImmutableDict { get; }
+        public ImmutableQueue<string> MyStringImmutablQueueT { get; set; }
+        public ImmutableSortedSet<string> MyStringImmutableSortedSetT { get; }
+        public List<string> MyListOfNullString { get; }
+
+        public ClassWithConstructor_SimpleAndComplexParameters(
+            byte myByte,
+            char myChar,
+            string myString,
+            decimal myDecimal,
+            bool myBooleanFalse,
+            double myDouble,
+            DateTimeOffset myDateTimeOffset,
+            Guid myGuid,
+            SampleEnum myEnum,
+            SampleEnumInt64 myInt64Enum,
+            SampleEnumUInt64 myUInt64Enum,
+            SimpleStruct mySimpleStruct,
+            int[][][] myInt16ThreeDimensionArray,
+            List<List<List<int>>> myInt16ThreeDimensionList,
+            List<string> myStringList,
+            IList myStringIList,
+            IEnumerable<string> myStringIEnumerableT,
+            IReadOnlyList<string> myStringIReadOnlyListT,
+            KeyValuePair<string, string> myStringToStringKeyValuePair,
+            Dictionary<string, string> myStringToStringGenericDict,
+            IImmutableDictionary<string, string> myStringToStringIImmutableDict,
+            ImmutableSortedSet<string> myStringImmutableSortedSetT,
+            List<string> myListOfNullString)
+        {
+            MyByte = myByte;
+            MyChar = myChar;
+            MyString = myString;
+            MyDecimal = myDecimal;
+            MyBooleanFalse = myBooleanFalse;
+            MyDouble = myDouble;
+            MyDateTimeOffset = myDateTimeOffset;
+            MyGuid = myGuid;
+            MyEnum = myEnum;
+            MyInt64Enum = myInt64Enum;
+            MyUInt64Enum = myUInt64Enum;
+            MySimpleStruct = mySimpleStruct;
+            MyInt16ThreeDimensionArray = myInt16ThreeDimensionArray;
+            MyInt16ThreeDimensionList = myInt16ThreeDimensionList;
+            MyStringList = myStringList;
+            MyStringIList = myStringIList;
+            MyStringIEnumerableT = myStringIEnumerableT;
+            MyStringIReadOnlyListT = myStringIReadOnlyListT;
+            MyStringToStringKeyValuePair = myStringToStringKeyValuePair;
+            MyStringToStringGenericDict = myStringToStringGenericDict;
+            MyStringToStringIImmutableDict = myStringToStringIImmutableDict;
+            MyStringImmutableSortedSetT = myStringImmutableSortedSetT;
+            MyListOfNullString = myListOfNullString;
+        }
+
+        public static ClassWithConstructor_SimpleAndComplexParameters GetInstance() =>
+            JsonSerializer.Deserialize<ClassWithConstructor_SimpleAndComplexParameters>(s_json);
+
+        public static readonly string s_json = $"{{{s_partialJson1},{s_partialJson2}}}";
+        public static readonly string s_json_flipped = $"{{{s_partialJson2},{s_partialJson1}}}";
+
+        private const string s_partialJson1 =
+            @"""MyByte"" : 7," +
+            @"""MySByte"" : 8," +
+            @"""MyChar"" : ""a""," +
+            @"""MyString"" : ""Hello""," +
+            @"""MyBooleanTrue"" : true," +
+            @"""MyBooleanFalse"" : false," +
+            @"""MySingle"" : 1.1," +
+            @"""MyDouble"" : 2.2," +
+            @"""MyDecimal"" : 3.3," +
+            @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," +
+            @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," +
+            @"""MyGuid"" : ""1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6""," +
+            @"""MyUri"" : ""https://github.com/dotnet/runtime""," +
+            @"""MyEnum"" : 2," + // int by default
+            @"""MyInt64Enum"" : -9223372036854775808," +
+            @"""MyUInt64Enum"" : 18446744073709551615," +
+            @"""MySimpleStruct"" : {""One"" : 11, ""Two"" : 1.9999, ""Three"" : 33}," +
+            @"""MySimpleTestStruct"" : {""MyInt64"" : 64, ""MyString"" :""Hello"", ""MyInt32Array"" : [32]}," +
+            @"""MyInt16ThreeDimensionArray"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]";
+
+        private const string s_partialJson2 =
+            @"""MyInt16ThreeDimensionList"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," +
+            @"""MyStringList"" : [""Hello""]," +
+            @"""MyStringIEnumerable"" : [""Hello""]," +
+            @"""MyStringIList"" : [""Hello""]," +
+            @"""MyStringICollection"" : [""Hello""]," +
+            @"""MyStringIEnumerableT"" : [""Hello""]," +
+            @"""MyStringIReadOnlyListT"" : [""Hello""]," +
+            @"""MyStringISetT"" : [""Hello""]," +
+            @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," +
+            @"""MyStringToStringIDict"" : {""key"" : ""value""}," +
+            @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," +
+            @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," +
+            @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," +
+            @"""MyStringImmutablQueueT"" : [""Hello""]," +
+            @"""MyStringImmutableSortedSetT"" : [""Hello""]," +
+            @"""MyListOfNullString"" : [null]";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal((byte)7, MyByte);
+            Assert.Equal((sbyte)8, MySByte);
+            Assert.Equal('a', MyChar);
+            Assert.Equal("Hello", MyString);
+            Assert.Equal(3.3m, MyDecimal);
+            Assert.False(MyBooleanFalse);
+            Assert.True(MyBooleanTrue);
+            Assert.Equal(1.1f, MySingle);
+            Assert.Equal(2.2d, MyDouble);
+            Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime);
+            Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffset);
+            Assert.Equal(SampleEnum.Two, MyEnum);
+            Assert.Equal(SampleEnumInt64.MinNegative, MyInt64Enum);
+            Assert.Equal(SampleEnumUInt64.Max, MyUInt64Enum);
+            Assert.Equal(11, MySimpleStruct.One);
+            Assert.Equal(1.9999, MySimpleStruct.Two);
+            Assert.Equal(64, MySimpleTestStruct.MyInt64);
+            Assert.Equal("Hello", MySimpleTestStruct.MyString);
+            Assert.Equal(32, MySimpleTestStruct.MyInt32Array[0]);
+
+            Assert.Equal(11, MyInt16ThreeDimensionArray[0][0][0]);
+            Assert.Equal(12, MyInt16ThreeDimensionArray[0][0][1]);
+            Assert.Equal(13, MyInt16ThreeDimensionArray[0][1][0]);
+            Assert.Equal(14, MyInt16ThreeDimensionArray[0][1][1]);
+            Assert.Equal(21, MyInt16ThreeDimensionArray[1][0][0]);
+            Assert.Equal(22, MyInt16ThreeDimensionArray[1][0][1]);
+            Assert.Equal(23, MyInt16ThreeDimensionArray[1][1][0]);
+            Assert.Equal(24, MyInt16ThreeDimensionArray[1][1][1]);
+
+            Assert.Equal(11, MyInt16ThreeDimensionList[0][0][0]);
+            Assert.Equal(12, MyInt16ThreeDimensionList[0][0][1]);
+            Assert.Equal(13, MyInt16ThreeDimensionList[0][1][0]);
+            Assert.Equal(14, MyInt16ThreeDimensionList[0][1][1]);
+            Assert.Equal(21, MyInt16ThreeDimensionList[1][0][0]);
+            Assert.Equal(22, MyInt16ThreeDimensionList[1][0][1]);
+            Assert.Equal(23, MyInt16ThreeDimensionList[1][1][0]);
+            Assert.Equal(24, MyInt16ThreeDimensionList[1][1][1]);
+
+            Assert.Equal("Hello", MyStringList[0]);
+
+            IEnumerator enumerator = MyStringIEnumerable.GetEnumerator();
+            enumerator.MoveNext();
+            Assert.Equal("Hello", ((JsonElement)enumerator.Current).GetString());
+
+            Assert.Equal("Hello", ((JsonElement)MyStringIList[0]).GetString());
+
+            enumerator = MyStringICollection.GetEnumerator();
+            enumerator.MoveNext();
+            Assert.Equal("Hello", ((JsonElement)enumerator.Current).GetString());
+
+            Assert.Equal("Hello", MyStringIEnumerableT.First());
+
+            Assert.Equal("Hello", MyStringIReadOnlyListT[0]);
+            Assert.Equal("Hello", MyStringISetT.First());
+
+            Assert.Equal("myKey", MyStringToStringKeyValuePair.Key);
+            Assert.Equal("myValue", MyStringToStringKeyValuePair.Value);
+
+            enumerator = MyStringToStringIDict.GetEnumerator();
+            enumerator.MoveNext();
+            DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
+            Assert.Equal("key", entry.Key);
+            Assert.Equal("value", ((JsonElement)entry.Value).GetString());
+
+            Assert.Equal("value", MyStringToStringGenericDict["key"]);
+            Assert.Equal("value", MyStringToStringGenericIDict["key"]);
+            Assert.Equal("value", MyStringToStringIImmutableDict["key"]);
+
+            Assert.Equal("Hello", MyStringImmutablQueueT.First());
+            Assert.Equal("Hello", MyStringImmutableSortedSetT.First());
+
+            Assert.Null(MyListOfNullString[0]);
+        }
+    }
+    public class Parameterless_ClassWithPrimitives
+    {
+        public int FirstInt { get; set; }
+        public int SecondInt { get; set; }
+
+        public string FirstString { get; set; }
+        public string SecondString { get; set; }
+
+        public DateTime FirstDateTime { get; set; }
+        public DateTime SecondDateTime { get; set; }
+
+        public int X { get; set; }
+        public int Y { get; set; }
+        public int Z { get; set; }
+
+        public int ThirdInt { get; set; }
+        public int FourthInt { get; set; }
+
+        public string ThirdString { get; set; }
+        public string FourthString { get; set; }
+
+        public DateTime ThirdDateTime { get; set; }
+        public DateTime FourthDateTime { get; set; }
+    }
+
+    public class Parameterized_ClassWithPrimitives_3Args
+    {
+        public int FirstInt { get; set; }
+        public int SecondInt { get; set; }
+
+        public string FirstString { get; set; }
+        public string SecondString { get; set; }
+
+        public DateTime FirstDateTime { get; set; }
+        public DateTime SecondDateTime { get; set; }
+
+        public int X { get; }
+        public int Y { get; }
+        public int Z { get; }
+
+        public int ThirdInt { get; set; }
+        public int FourthInt { get; set; }
+
+        public string ThirdString { get; set; }
+        public string FourthString { get; set; }
+
+        public DateTime ThirdDateTime { get; set; }
+        public DateTime FourthDateTime { get; set; }
+
+
+        public Parameterized_ClassWithPrimitives_3Args(int x, int y, int z) => (X, Y, Z) = (x, y, z);
+    }
+
+    public class TupleWrapper
+    {
+        public Tuple<string, double> Tuple { get; set; }
+    }
+
+    public class ConverterForPoint3D : JsonConverter<Point_3D>
+    {
+        public override Point_3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            if (reader.TokenType != JsonTokenType.StartObject)
+            {
+                throw new JsonException();
+            }
+
+            while (reader.TokenType != JsonTokenType.EndObject)
+            {
+                reader.Read();
+            }
+
+            return new Point_3D(4, 4, 4);
+        }
+
+        public override void Write(Utf8JsonWriter writer, Point_3D value, JsonSerializerOptions options)
+        {
+            throw new NotImplementedException();
+        }
+    }
+    public class ConverterForInt32 : JsonConverter<int>
+    {
+        public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            return 25;
+        }
+
+        public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class Class_With_Ctor_With_64_Params : ITestClass
+    {
+        public int Int0 { get; }
+        public int Int1 { get; }
+        public int Int2 { get; }
+        public int Int3 { get; }
+        public int Int4 { get; }
+        public int Int5 { get; }
+        public int Int6 { get; }
+        public int Int7 { get; }
+        public int Int8 { get; }
+        public int Int9 { get; }
+        public int Int10 { get; }
+        public int Int11 { get; }
+        public int Int12 { get; }
+        public int Int13 { get; }
+        public int Int14 { get; }
+        public int Int15 { get; }
+        public int Int16 { get; }
+        public int Int17 { get; }
+        public int Int18 { get; }
+        public int Int19 { get; }
+        public int Int20 { get; }
+        public int Int21 { get; }
+        public int Int22 { get; }
+        public int Int23 { get; }
+        public int Int24 { get; }
+        public int Int25 { get; }
+        public int Int26 { get; }
+        public int Int27 { get; }
+        public int Int28 { get; }
+        public int Int29 { get; }
+        public int Int30 { get; }
+        public int Int31 { get; }
+        public int Int32 { get; }
+        public int Int33 { get; }
+        public int Int34 { get; }
+        public int Int35 { get; }
+        public int Int36 { get; }
+        public int Int37 { get; }
+        public int Int38 { get; }
+        public int Int39 { get; }
+        public int Int40 { get; }
+        public int Int41 { get; }
+        public int Int42 { get; }
+        public int Int43 { get; }
+        public int Int44 { get; }
+        public int Int45 { get; }
+        public int Int46 { get; }
+        public int Int47 { get; }
+        public int Int48 { get; }
+        public int Int49 { get; }
+        public int Int50 { get; }
+        public int Int51 { get; }
+        public int Int52 { get; }
+        public int Int53 { get; }
+        public int Int54 { get; }
+        public int Int55 { get; }
+        public int Int56 { get; }
+        public int Int57 { get; }
+        public int Int58 { get; }
+        public int Int59 { get; }
+        public int Int60 { get; }
+        public int Int61 { get; }
+        public int Int62 { get; }
+        public int Int63 { get; }
+
+        public Class_With_Ctor_With_64_Params(int int0, int int1, int int2, int int3, int int4, int int5, int int6, int int7,
+                                             int int8, int int9, int int10, int int11, int int12, int int13, int int14, int int15,
+                                             int int16, int int17, int int18, int int19, int int20, int int21, int int22, int int23,
+                                             int int24, int int25, int int26, int int27, int int28, int int29, int int30, int int31,
+                                             int int32, int int33, int int34, int int35, int int36, int int37, int int38, int int39,
+                                             int int40, int int41, int int42, int int43, int int44, int int45, int int46, int int47,
+                                             int int48, int int49, int int50, int int51, int int52, int int53, int int54, int int55,
+                                             int int56, int int57, int int58, int int59, int int60, int int61, int int62, int int63)
+        {
+            Int0 = int0; Int1 = int1; Int2 = int2; Int3 = int3; Int4 = int4; Int5 = int5; Int6 = int6; Int7 = int7;
+            Int8 = int8; Int9 = int9; Int10 = int10; Int11 = int11; Int12 = int12; Int13 = int13; Int14 = int14; Int15 = int15;
+            Int16 = int16; Int17 = int17; Int18 = int18; Int19 = int19; Int20 = int20; Int21 = int21; Int22 = int22; Int23 = int23;
+            Int24 = int24; Int25 = int25; Int26 = int26; Int27 = int27; Int28 = int28; Int29 = int29; Int30 = int30; Int31 = int31;
+            Int32 = int32; Int33 = int33; Int34 = int34; Int35 = int35; Int36 = int36; Int37 = int37; Int38 = int38; Int39 = int39;
+            Int40 = int40; Int41 = int41; Int42 = int42; Int43 = int43; Int44 = int44; Int45 = int45; Int46 = int46; Int47 = int47;
+            Int48 = int48; Int49 = int49; Int50 = int50; Int51 = int51; Int52 = int52; Int53 = int53; Int54 = int54; Int55 = int55;
+            Int56 = int56; Int57 = int57; Int58 = int58; Int59 = int59; Int60 = int60; Int61 = int61; Int62 = int62; Int63 = int63;
+        }
+
+        public static string Json {
+            get
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.Append("{");
+                for (int i = 0; i < 63; i++)
+                {
+                    sb.Append($@"""Int{i}"":{i},");
+                }
+                sb.Append($@"""Int63"":63");
+                sb.Append("}");
+
+                return sb.ToString();
+            }
+        }
+
+        public static byte[] Data => Encoding.UTF8.GetBytes(Json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            for (int i = 0; i < 64; i++)
+            {
+                Assert.Equal(i, (int)typeof(Class_With_Ctor_With_64_Params).GetProperty($"Int{i}").GetValue(this));
+            }
+        }
+    }
+
+    public struct Struct_With_Ctor_With_64_Params
+    {
+        public int Int0 { get; }
+        public int Int1 { get; }
+        public int Int2 { get; }
+        public int Int3 { get; }
+        public int Int4 { get; }
+        public int Int5 { get; }
+        public int Int6 { get; }
+        public int Int7 { get; }
+        public int Int8 { get; }
+        public int Int9 { get; }
+        public int Int10 { get; }
+        public int Int11 { get; }
+        public int Int12 { get; }
+        public int Int13 { get; }
+        public int Int14 { get; }
+        public int Int15 { get; }
+        public int Int16 { get; }
+        public int Int17 { get; }
+        public int Int18 { get; }
+        public int Int19 { get; }
+        public int Int20 { get; }
+        public int Int21 { get; }
+        public int Int22 { get; }
+        public int Int23 { get; }
+        public int Int24 { get; }
+        public int Int25 { get; }
+        public int Int26 { get; }
+        public int Int27 { get; }
+        public int Int28 { get; }
+        public int Int29 { get; }
+        public int Int30 { get; }
+        public int Int31 { get; }
+        public int Int32 { get; }
+        public int Int33 { get; }
+        public int Int34 { get; }
+        public int Int35 { get; }
+        public int Int36 { get; }
+        public int Int37 { get; }
+        public int Int38 { get; }
+        public int Int39 { get; }
+        public int Int40 { get; }
+        public int Int41 { get; }
+        public int Int42 { get; }
+        public int Int43 { get; }
+        public int Int44 { get; }
+        public int Int45 { get; }
+        public int Int46 { get; }
+        public int Int47 { get; }
+        public int Int48 { get; }
+        public int Int49 { get; }
+        public int Int50 { get; }
+        public int Int51 { get; }
+        public int Int52 { get; }
+        public int Int53 { get; }
+        public int Int54 { get; }
+        public int Int55 { get; }
+        public int Int56 { get; }
+        public int Int57 { get; }
+        public int Int58 { get; }
+        public int Int59 { get; }
+        public int Int60 { get; }
+        public int Int61 { get; }
+        public int Int62 { get; }
+        public int Int63 { get; }
+
+        [JsonConstructor]
+        public Struct_With_Ctor_With_64_Params(int int0, int int1, int int2, int int3, int int4, int int5, int int6, int int7,
+                                             int int8, int int9, int int10, int int11, int int12, int int13, int int14, int int15,
+                                             int int16, int int17, int int18, int int19, int int20, int int21, int int22, int int23,
+                                             int int24, int int25, int int26, int int27, int int28, int int29, int int30, int int31,
+                                             int int32, int int33, int int34, int int35, int int36, int int37, int int38, int int39,
+                                             int int40, int int41, int int42, int int43, int int44, int int45, int int46, int int47,
+                                             int int48, int int49, int int50, int int51, int int52, int int53, int int54, int int55,
+                                             int int56, int int57, int int58, int int59, int int60, int int61, int int62, int int63)
+        {
+            Int0 = int0; Int1 = int1; Int2 = int2; Int3 = int3; Int4 = int4; Int5 = int5; Int6 = int6; Int7 = int7;
+            Int8 = int8; Int9 = int9; Int10 = int10; Int11 = int11; Int12 = int12; Int13 = int13; Int14 = int14; Int15 = int15;
+            Int16 = int16; Int17 = int17; Int18 = int18; Int19 = int19; Int20 = int20; Int21 = int21; Int22 = int22; Int23 = int23;
+            Int24 = int24; Int25 = int25; Int26 = int26; Int27 = int27; Int28 = int28; Int29 = int29; Int30 = int30; Int31 = int31;
+            Int32 = int32; Int33 = int33; Int34 = int34; Int35 = int35; Int36 = int36; Int37 = int37; Int38 = int38; Int39 = int39;
+            Int40 = int40; Int41 = int41; Int42 = int42; Int43 = int43; Int44 = int44; Int45 = int45; Int46 = int46; Int47 = int47;
+            Int48 = int48; Int49 = int49; Int50 = int50; Int51 = int51; Int52 = int52; Int53 = int53; Int54 = int54; Int55 = int55;
+            Int56 = int56; Int57 = int57; Int58 = int58; Int59 = int59; Int60 = int60; Int61 = int61; Int62 = int62; Int63 = int63;
+        }
+    }
+
+    public class Class_With_Ctor_With_65_Params
+    {
+        public int Int0 { get; }
+        public int Int1 { get; }
+        public int Int2 { get; }
+        public int Int3 { get; }
+        public int Int4 { get; }
+        public int Int5 { get; }
+        public int Int6 { get; }
+        public int Int7 { get; }
+        public int Int8 { get; }
+        public int Int9 { get; }
+        public int Int10 { get; }
+        public int Int11 { get; }
+        public int Int12 { get; }
+        public int Int13 { get; }
+        public int Int14 { get; }
+        public int Int15 { get; }
+        public int Int16 { get; }
+        public int Int17 { get; }
+        public int Int18 { get; }
+        public int Int19 { get; }
+        public int Int20 { get; }
+        public int Int21 { get; }
+        public int Int22 { get; }
+        public int Int23 { get; }
+        public int Int24 { get; }
+        public int Int25 { get; }
+        public int Int26 { get; }
+        public int Int27 { get; }
+        public int Int28 { get; }
+        public int Int29 { get; }
+        public int Int30 { get; }
+        public int Int31 { get; }
+        public int Int32 { get; }
+        public int Int33 { get; }
+        public int Int34 { get; }
+        public int Int35 { get; }
+        public int Int36 { get; }
+        public int Int37 { get; }
+        public int Int38 { get; }
+        public int Int39 { get; }
+        public int Int40 { get; }
+        public int Int41 { get; }
+        public int Int42 { get; }
+        public int Int43 { get; }
+        public int Int44 { get; }
+        public int Int45 { get; }
+        public int Int46 { get; }
+        public int Int47 { get; }
+        public int Int48 { get; }
+        public int Int49 { get; }
+        public int Int50 { get; }
+        public int Int51 { get; }
+        public int Int52 { get; }
+        public int Int53 { get; }
+        public int Int54 { get; }
+        public int Int55 { get; }
+        public int Int56 { get; }
+        public int Int57 { get; }
+        public int Int58 { get; }
+        public int Int59 { get; }
+        public int Int60 { get; }
+        public int Int61 { get; }
+        public int Int62 { get; }
+        public int Int63 { get; }
+        public int Int64 { get; }
+
+        public Class_With_Ctor_With_65_Params(int int0, int int1, int int2, int int3, int int4, int int5, int int6, int int7,
+                                             int int8, int int9, int int10, int int11, int int12, int int13, int int14, int int15,
+                                             int int16, int int17, int int18, int int19, int int20, int int21, int int22, int int23,
+                                             int int24, int int25, int int26, int int27, int int28, int int29, int int30, int int31,
+                                             int int32, int int33, int int34, int int35, int int36, int int37, int int38, int int39,
+                                             int int40, int int41, int int42, int int43, int int44, int int45, int int46, int int47,
+                                             int int48, int int49, int int50, int int51, int int52, int int53, int int54, int int55,
+                                             int int56, int int57, int int58, int int59, int int60, int int61, int int62, int int63,
+                                             int int64)
+        {
+            Int0 = int0; Int1 = int1; Int2 = int2; Int3 = int3; Int4 = int4; Int5 = int5; Int6 = int6; Int7 = int7;
+            Int8 = int8; Int9 = int9; Int10 = int10; Int11 = int11; Int12 = int12; Int13 = int13; Int14 = int14; Int15 = int15;
+            Int16 = int16; Int17 = int17; Int18 = int18; Int19 = int19; Int20 = int20; Int21 = int21; Int22 = int22; Int23 = int23;
+            Int24 = int24; Int25 = int25; Int26 = int26; Int27 = int27; Int28 = int28; Int29 = int29; Int30 = int30; Int31 = int31;
+            Int32 = int32; Int33 = int33; Int34 = int34; Int35 = int35; Int36 = int36; Int37 = int37; Int38 = int38; Int39 = int39;
+            Int40 = int40; Int41 = int41; Int42 = int42; Int43 = int43; Int44 = int44; Int45 = int45; Int46 = int46; Int47 = int47;
+            Int48 = int48; Int49 = int49; Int50 = int50; Int51 = int51; Int52 = int52; Int53 = int53; Int54 = int54; Int55 = int55;
+            Int56 = int56; Int57 = int57; Int58 = int58; Int59 = int59; Int60 = int60; Int61 = int61; Int62 = int62; Int63 = int63;
+            Int64 = int64;
+        }
+    }
+
+    public struct Struct_With_Ctor_With_65_Params
+    {
+        public int Int0 { get; }
+        public int Int1 { get; }
+        public int Int2 { get; }
+        public int Int3 { get; }
+        public int Int4 { get; }
+        public int Int5 { get; }
+        public int Int6 { get; }
+        public int Int7 { get; }
+        public int Int8 { get; }
+        public int Int9 { get; }
+        public int Int10 { get; }
+        public int Int11 { get; }
+        public int Int12 { get; }
+        public int Int13 { get; }
+        public int Int14 { get; }
+        public int Int15 { get; }
+        public int Int16 { get; }
+        public int Int17 { get; }
+        public int Int18 { get; }
+        public int Int19 { get; }
+        public int Int20 { get; }
+        public int Int21 { get; }
+        public int Int22 { get; }
+        public int Int23 { get; }
+        public int Int24 { get; }
+        public int Int25 { get; }
+        public int Int26 { get; }
+        public int Int27 { get; }
+        public int Int28 { get; }
+        public int Int29 { get; }
+        public int Int30 { get; }
+        public int Int31 { get; }
+        public int Int32 { get; }
+        public int Int33 { get; }
+        public int Int34 { get; }
+        public int Int35 { get; }
+        public int Int36 { get; }
+        public int Int37 { get; }
+        public int Int38 { get; }
+        public int Int39 { get; }
+        public int Int40 { get; }
+        public int Int41 { get; }
+        public int Int42 { get; }
+        public int Int43 { get; }
+        public int Int44 { get; }
+        public int Int45 { get; }
+        public int Int46 { get; }
+        public int Int47 { get; }
+        public int Int48 { get; }
+        public int Int49 { get; }
+        public int Int50 { get; }
+        public int Int51 { get; }
+        public int Int52 { get; }
+        public int Int53 { get; }
+        public int Int54 { get; }
+        public int Int55 { get; }
+        public int Int56 { get; }
+        public int Int57 { get; }
+        public int Int58 { get; }
+        public int Int59 { get; }
+        public int Int60 { get; }
+        public int Int61 { get; }
+        public int Int62 { get; }
+        public int Int63 { get; }
+        public int Int64 { get; }
+
+        [JsonConstructor]
+        public Struct_With_Ctor_With_65_Params(int int0, int int1, int int2, int int3, int int4, int int5, int int6, int int7,
+                                             int int8, int int9, int int10, int int11, int int12, int int13, int int14, int int15,
+                                             int int16, int int17, int int18, int int19, int int20, int int21, int int22, int int23,
+                                             int int24, int int25, int int26, int int27, int int28, int int29, int int30, int int31,
+                                             int int32, int int33, int int34, int int35, int int36, int int37, int int38, int int39,
+                                             int int40, int int41, int int42, int int43, int int44, int int45, int int46, int int47,
+                                             int int48, int int49, int int50, int int51, int int52, int int53, int int54, int int55,
+                                             int int56, int int57, int int58, int int59, int int60, int int61, int int62, int int63,
+                                             int int64)
+        {
+            Int0 = int0; Int1 = int1; Int2 = int2; Int3 = int3; Int4 = int4; Int5 = int5; Int6 = int6; Int7 = int7;
+            Int8 = int8; Int9 = int9; Int10 = int10; Int11 = int11; Int12 = int12; Int13 = int13; Int14 = int14; Int15 = int15;
+            Int16 = int16; Int17 = int17; Int18 = int18; Int19 = int19; Int20 = int20; Int21 = int21; Int22 = int22; Int23 = int23;
+            Int24 = int24; Int25 = int25; Int26 = int26; Int27 = int27; Int28 = int28; Int29 = int29; Int30 = int30; Int31 = int31;
+            Int32 = int32; Int33 = int33; Int34 = int34; Int35 = int35; Int36 = int36; Int37 = int37; Int38 = int38; Int39 = int39;
+            Int40 = int40; Int41 = int41; Int42 = int42; Int43 = int43; Int44 = int44; Int45 = int45; Int46 = int46; Int47 = int47;
+            Int48 = int48; Int49 = int49; Int50 = int50; Int51 = int51; Int52 = int52; Int53 = int53; Int54 = int54; Int55 = int55;
+            Int56 = int56; Int57 = int57; Int58 = int58; Int59 = int59; Int60 = int60; Int61 = int61; Int62 = int62; Int63 = int63;
+            Int64 = int64;
+        }
+    }
+
+    public class Parameterized_Person : ITestClass
+    {
+        public string FirstName { get; set; }
+
+        public string LastName { get; set; }
+
+        public Guid Id { get; }
+
+        [JsonExtensionData]
+        public Dictionary<string, JsonElement> ExtensionData { get; set; }
+
+        public Parameterized_Person(Guid id) => Id = id;
+
+        public static readonly string s_json = @"{
+            ""FirstName"":""Jet"",
+            ""Id"":""270bb22b-4816-4bd9-9acd-8ec5b1a896d3"",
+            ""EmailAddress"":""jetdoe@outlook.com"",
+            ""Id"":""0b3aa420-2e98-47f7-8a49-fea233b89416"",
+            ""LastName"":""Doe"",
+            ""Id"":""63cf821d-fd47-4782-8345-576d9228a534""
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal("Jet", FirstName);
+            Assert.Equal("Doe", LastName);
+            Assert.Equal("63cf821d-fd47-4782-8345-576d9228a534", Id.ToString());
+            Assert.Equal("jetdoe@outlook.com", ExtensionData["EmailAddress"].GetString());
+            Assert.False(ExtensionData.ContainsKey("Id"));
+        }
+    }
+
+    public class Parameterized_Person_ObjExtData : ITestClass
+    {
+        public string FirstName { get; set; }
+
+        public string LastName { get; set; }
+
+        public Guid Id { get; }
+
+        [JsonExtensionData]
+        public Dictionary<string, object> ExtensionData { get; set; }
+
+        public Parameterized_Person_ObjExtData(Guid id) => Id = id;
+
+        public static readonly string s_json = @"{
+            ""FirstName"":""Jet"",
+            ""Id"":""270bb22b-4816-4bd9-9acd-8ec5b1a896d3"",
+            ""EmailAddress"":""jetdoe@outlook.com"",
+            ""Id"":""0b3aa420-2e98-47f7-8a49-fea233b89416"",
+            ""LastName"":""Doe"",
+            ""Id"":""63cf821d-fd47-4782-8345-576d9228a534""
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal("Jet", FirstName);
+            Assert.Equal("Doe", LastName);
+            Assert.Equal("63cf821d-fd47-4782-8345-576d9228a534", Id.ToString());
+            Assert.Equal("jetdoe@outlook.com", ((JsonElement)ExtensionData["EmailAddress"]).GetString());
+            Assert.False(ExtensionData.ContainsKey("Id"));
+        }
+    }
+
+    public class Parameterized_Person_Simple : ITestClass
+    {
+        public string FirstName { get; set; }
+
+        public string LastName { get; set; }
+
+        public Guid Id { get; }
+
+        public Parameterized_Person_Simple(Guid id) => Id = id;
+
+        public static readonly string s_json = @"{
+            ""FirstName"":""Jet"",
+            ""Id"":""270bb22b-4816-4bd9-9acd-8ec5b1a896d3"",
+            ""LastName"":""Doe""
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize()
+        {
+            FirstName = "Jet";
+            LastName = "Doe";
+        }
+
+        public void Verify()
+        {
+            Assert.Equal("Jet", FirstName);
+            Assert.Equal("Doe", LastName);
+            Assert.Equal("270bb22b-4816-4bd9-9acd-8ec5b1a896d3", Id.ToString());
+        }
+    }
+
+    public class SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt
+    {
+        public int X { get; }
+
+        [JsonExtensionData]
+        public Dictionary<string, JsonElement> ExtensionData { get; set; }
+
+        [JsonConstructor]
+        public SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt(int x) { }
+    }
+
+    public class SimpleClassWithParameterizedCtor_GenericDictionary_ObjectExt
+    {
+        public int X { get; }
+
+        [JsonExtensionData]
+        public Dictionary<string, object> ExtensionData { get; set; }
+
+        [JsonConstructor]
+        public SimpleClassWithParameterizedCtor_GenericDictionary_ObjectExt(int x) { }
+    }
+
+    public class SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_JsonElementExt
+    {
+        public int X { get; }
+
+        [JsonExtensionData]
+        public GenericIDictionaryWrapper<string, JsonElement> ExtensionData { get; set; }
+
+        [JsonConstructor]
+        public SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_JsonElementExt(int x) { }
+    }
+
+    public class SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_ObjectExt
+    {
+        public int X { get; }
+
+        [JsonExtensionData]
+        public GenericIDictionaryWrapper<string, object> ExtensionData { get; set; }
+
+        [JsonConstructor]
+        public SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_ObjectExt(int x) { }
+    }
+
+    public class Parameterized_IndexViewModel_Immutable : ITestClass
+    {
+        public List<ActiveOrUpcomingEvent> ActiveOrUpcomingEvents { get; }
+        public CampaignSummaryViewModel FeaturedCampaign { get; }
+        public bool IsNewAccount { get; }
+        public bool HasFeaturedCampaign => FeaturedCampaign != null;
+
+        public Parameterized_IndexViewModel_Immutable(
+            List<ActiveOrUpcomingEvent> activeOrUpcomingEvents,
+            CampaignSummaryViewModel featuredCampaign,
+            bool isNewAccount)
+        {
+            ActiveOrUpcomingEvents = activeOrUpcomingEvents;
+            FeaturedCampaign = featuredCampaign;
+            IsNewAccount = isNewAccount;
+        }
+
+        public static readonly string s_json =
+            @"{
+              ""ActiveOrUpcomingEvents"": [
+                {
+                  ""Id"": 10,
+                  ""ImageUrl"": ""https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png"",
+                  ""Name"": ""Just a name"",
+                  ""CampaignName"": ""The very new campaing"",
+                  ""CampaignManagedOrganizerName"": ""Name FamiltyName"",
+                  ""Description"": ""The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities."",
+                  ""StartDate"": ""2019-01-30T12:01:02+00:00"",
+                  ""EndDate"": ""2019-01-30T12:01:02+00:00""
+                }
+              ],
+              ""FeaturedCampaign"": {
+                ""Id"": 234235,
+                ""Title"": ""Promoting Open Source"",
+                ""Description"": ""Very nice campaing"",
+                ""ImageUrl"": ""https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png"",
+                ""OrganizationName"": ""The Company XYZ"",
+                ""Headline"": ""The Headline""
+              },
+              ""IsNewAccount"": false,
+              ""HasFeaturedCampaign"": true
+            }";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.False(IsNewAccount);
+
+            ActiveOrUpcomingEvent @event = ActiveOrUpcomingEvents.First();
+            Assert.Equal(10, @event.Id);
+            Assert.Equal("Name FamiltyName", @event.CampaignManagedOrganizerName);
+            Assert.Equal("The very new campaing", @event.CampaignName);
+            Assert.Equal("The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", @event.Description);
+            Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), @event.EndDate);
+            Assert.Equal("Just a name", @event.Name);
+            Assert.Equal("https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", @event.ImageUrl);
+            Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), @event.StartDate);
+
+            Assert.Equal("Very nice campaing", FeaturedCampaign.Description);
+            Assert.Equal("The Headline", FeaturedCampaign.Headline);
+            Assert.Equal(234235, FeaturedCampaign.Id);
+            Assert.Equal("The Company XYZ", FeaturedCampaign.OrganizationName);
+            Assert.Equal("https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", FeaturedCampaign.ImageUrl);
+            Assert.Equal("Promoting Open Source", FeaturedCampaign.Title);
+        }
+    }
+
+    public class ActiveOrUpcomingEvent
+    {
+        public int Id { get; set; }
+        public string ImageUrl { get; set; }
+        public string Name { get; set; }
+        public string CampaignName { get; set; }
+        public string CampaignManagedOrganizerName { get; set; }
+        public string Description { get; set; }
+        public DateTimeOffset StartDate { get; set; }
+        public DateTimeOffset EndDate { get; set; }
+    }
+
+    public class CampaignSummaryViewModel
+    {
+        public int Id { get; set; }
+        public string Title { get; set; }
+        public string Description { get; set; }
+        public string ImageUrl { get; set; }
+        public string OrganizationName { get; set; }
+        public string Headline { get; set; }
+    }
+
+    public class Parameterized_Class_With_ComplexTuple : ITestClass
+    {
+        public Tuple<
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters> MyTuple { get; }
+
+        public Parameterized_Class_With_ComplexTuple(
+            Tuple<
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters,
+                ClassWithConstructor_SimpleAndComplexParameters> myTuple) => MyTuple = myTuple;
+
+        private static readonly string s_inner_json = @"
+            {
+                ""MyByte"": 7,
+                ""MyChar"": ""a"",
+                ""MyString"": ""Hello"",
+                ""MyDecimal"": 3.3,
+                ""MyBooleanFalse"": false,
+                ""MyDouble"": 2.2,
+                ""MyDateTimeOffset"": ""2019-01-30T12:01:02+01:00"",
+                ""MyGuid"": ""1b33498a-7b7d-4dda-9c13-f6aa4ab449a6"",
+                ""MyEnum"": 2,
+                ""MyInt64Enum"": -9223372036854775808,
+                ""MyUInt64Enum"": 18446744073709551615,
+                ""MySimpleStruct"": { ""One"": 11, ""Two"": 1.9999 },
+                ""MyInt16ThreeDimensionArray"": [ [ [ 11, 12 ], [ 13, 14 ] ], [ [ 21, 22 ], [ 23, 24 ] ] ],
+                ""MyInt16ThreeDimensionList"": [ [ [ 11, 12 ], [ 13, 14 ] ], [ [ 21, 22 ], [ 23, 24 ] ] ],
+                ""MyStringList"": [ ""Hello"" ],
+                ""MyStringIList"": [ ""Hello"" ],
+                ""MyStringIEnumerableT"": [ ""Hello"" ],
+                ""MyStringIReadOnlyListT"": [ ""Hello"" ],
+                ""MyStringToStringKeyValuePair"": { ""Key"": ""myKey"", ""Value"": ""myValue"" },
+                ""MyStringToStringGenericDict"": { ""key"": ""value"" },
+                ""MyStringToStringIImmutableDict"": { ""key"": ""value"" },
+                ""MyStringImmutableSortedSetT"": [ ""Hello"" ],
+                ""MyListOfNullString"": [ null ],
+                ""MySByte"": 8,
+                ""MyBooleanTrue"": true,
+                ""MySingle"": 1.1,
+                ""MyDateTime"": ""2019-01-30T12:01:02Z"",
+                ""MyUri"": ""https://github.com/dotnet/runtime"",
+                ""MySimpleTestStruct"": {
+                  ""MyInt16"": 0,
+                  ""MyInt32"": 0,
+                  ""MyInt64"": 64,
+                  ""MyUInt16"": 0,
+                  ""MyUInt32"": 0,
+                  ""MyUInt64"": 0,
+                  ""MyByte"": 0,
+                  ""MySByte"": 0,
+                  ""MyChar"": ""\u0000"",
+                  ""MyString"": ""Hello"",
+                  ""MyDecimal"": 0,
+                  ""MyBooleanTrue"": false,
+                  ""MyBooleanFalse"": false,
+                  ""MySingle"": 0,
+                  ""MyDouble"": 0,
+                  ""MyDateTime"": ""0001-01-01T00:00:00"",
+                  ""MyDateTimeOffset"": ""0001-01-01T00:00:00+00:00"",
+                  ""MyEnum"": 0,
+                  ""MyInt64Enum"": 0,
+                  ""MyUInt64Enum"": 0,
+                  ""MySimpleStruct"": {
+                    ""One"": 0,
+                    ""Two"": 0
+                  },
+                  ""MyInt32Array"": [ 32 ]
+                },
+                ""MyStringIEnumerable"": [ ""Hello"" ],
+                ""MyStringICollection"": [ ""Hello"" ],
+                ""MyStringISetT"": [ ""Hello"" ],
+                ""MyStringToStringIDict"": { ""key"": ""value"" },
+                ""MyStringToStringGenericIDict"": { ""key"": ""value"" },
+                ""MyStringImmutablQueueT"": [ ""Hello"" ]
+              }";
+
+        public static readonly string s_json =
+            $@"{{
+                ""MyTuple"": {{
+                    ""Item1"":{s_inner_json},
+                    ""Item2"":{s_inner_json},
+                    ""Item3"":{s_inner_json},
+                    ""Item4"":{s_inner_json},
+                    ""Item5"":{s_inner_json},
+                    ""Item6"":{s_inner_json},
+                    ""Item7"":{s_inner_json}
+                }}
+            }}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            MyTuple.Item1.Verify();
+            MyTuple.Item2.Verify();
+            MyTuple.Item3.Verify();
+            MyTuple.Item4.Verify();
+            MyTuple.Item5.Verify();
+            MyTuple.Item6.Verify();
+            MyTuple.Item7.Verify();
+        }
+    }
+
+    public class Point_MembersHave_JsonPropertyName : ITestClass
+    {
+        [JsonPropertyName("XValue")]
+        public int X { get; }
+
+        [JsonPropertyName("YValue")]
+        public int Y { get; }
+
+        public Point_MembersHave_JsonPropertyName(int x, int y) => (X, Y) = (x, y);
+
+        public void Initialize() { }
+
+        public static readonly string s_json = @"{""XValue"":1,""YValue"":2}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+        }
+    }
+
+    public class Point_MembersHave_JsonConverter : ITestClass
+    {
+        [JsonConverter(typeof(ConverterForInt32))]
+        public int X { get; }
+
+        [JsonConverter(typeof(ConverterForInt32))]
+        public int Y { get; }
+
+        public Point_MembersHave_JsonConverter(int x, int y) => (X, Y) = (x, y);
+
+        public void Initialize() { }
+
+        public static readonly string s_json = @"{""X"":1,""Y"":2}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Verify()
+        {
+            Assert.Equal(25, X);
+            Assert.Equal(25, Y);
+        }
+    }
+
+    public class Point_MembersHave_JsonIgnore : ITestClass
+    {
+        [JsonIgnore]
+        public int X { get; }
+
+        [JsonIgnore]
+        public int Y { get; }
+
+        public Point_MembersHave_JsonIgnore(int x, int y = 5) => (X, Y) = (x, y);
+
+        public void Initialize() { }
+
+        public static readonly string s_json = @"{""X"":1,""Y"":2}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Verify()
+        {
+            Assert.Equal(0, X);
+            Assert.Equal(0, Y); // We don't set parameter default value here.
+        }
+    }
+
+    public class Point_MultipleMembers_BindTo_OneConstructorParameter
+    {
+        public int X { get; }
+
+        public int x { get; }
+
+        public Point_MultipleMembers_BindTo_OneConstructorParameter(int X, int x) { }
+    }
+
+    public class Point_MultipleMembers_BindTo_OneConstructorParameter_Variant
+    {
+        public int X { get; }
+
+        public int x { get; }
+
+        public Point_MultipleMembers_BindTo_OneConstructorParameter_Variant(int x) { }
+    }
+
+    public class Point_Without_Members
+    {
+        public Point_Without_Members(int x, int y) { }
+    }
+
+    public class Point_With_MismatchedMembers
+    {
+        public int X { get; }
+        public float Y { get; }
+
+        public Point_With_MismatchedMembers(int x, int y) { }
+    }
+
+    public class WrapperFor_Point_With_MismatchedMembers
+    {
+        public int MyInt { get; set; }
+        public Point_With_MismatchedMembers MyPoint { get; set; }
+    }
+
+
+    public class Point_ExtendedPropNames
+    {
+        public int XValue { get; }
+        public int YValue { get; }
+
+        public Point_ExtendedPropNames(int xValue, int yValue)
+        {
+            XValue = xValue;
+            YValue = yValue;
+        }
+    }
+
+    public class LowerCaseNamingPolicy : JsonNamingPolicy
+    {
+        public override string ConvertName(string name)
+        {
+            return name.ToLowerInvariant();
+        }
+    }
+
+    public class SimpleSnakeCasePolicy : JsonNamingPolicy
+    {
+        public override string ConvertName(string name)
+        {
+            return string.Concat(name.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
+        }
+    }
+
+    public class Point_With_Array : ITestClass
+    {
+        public int X { get; }
+        public int Y { get; }
+
+        public int[] Arr { get; }
+
+        public static readonly string s_json = @"{""X"":1,""Y"":2,""Arr"":[1,2]}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public Point_With_Array(int x, int y, int[] arr)
+        {
+            X = x;
+            Y = y;
+            Arr = arr;
+        }
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+            Assert.Equal(1, Arr[0]);
+            Assert.Equal(2, Arr[1]);
+        }
+    }
+
+    public class Point_With_Dictionary : ITestClass
+    {
+        public int X { get; }
+        public int Y { get; }
+
+        public Dictionary<string, int> Dict { get; }
+
+        public static readonly string s_json = @"{""X"":1,""Y"":2,""Dict"":{""1"":1,""2"":2}}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public Point_With_Dictionary(int x, int y, Dictionary<string, int> dict)
+        {
+            X = x;
+            Y = y;
+            Dict = dict;
+        }
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+            Assert.Equal(1, Dict["1"]);
+            Assert.Equal(2, Dict["2"]);
+        }
+    }
+
+    public class Point_With_Object : ITestClass
+    {
+        public int X { get; }
+        public int Y { get; }
+
+        public Point_With_Array Obj { get; }
+
+        public static readonly string s_json = @$"{{""X"":1,""Y"":2,""Obj"":{Point_With_Array.s_json}}}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public Point_With_Object(int x, int y, Point_With_Array obj)
+        {
+            X = x;
+            Y = y;
+            Obj = obj;
+        }
+
+        public void Initialize() { }
+
+        public void Verify()
+        {
+            Assert.Equal(1, X);
+            Assert.Equal(2, Y);
+            Obj.Verify();
+        }
+    }
+
+    public struct Point_With_Property
+    {
+        public int X { get; }
+        public int Y { get; }
+        public int Z { get; set;  }
+
+        [JsonConstructor]
+        public Point_With_Property(int x, int y)
+        {
+            X = x;
+            Y = y;
+            Z = 0;
+        }
+    }
+
+    public class MyEventsListerViewModel
+    {
+        public List<MyEventsListerItem> CurrentEvents { get; set; } = new List<MyEventsListerItem>();
+        public List<MyEventsListerItem> FutureEvents { get; set; } = new List<MyEventsListerItem>();
+        public List<MyEventsListerItem> PastEvents { get; set; } = new List<MyEventsListerItem>();
+
+        public static MyEventsListerViewModel Instance
+            = new MyEventsListerViewModel
+            {
+                CurrentEvents = Enumerable.Repeat(MyEventsListerItem.Instance, 3).ToList(),
+                FutureEvents = Enumerable.Repeat(MyEventsListerItem.Instance, 9).ToList(),
+                PastEvents = Enumerable.Repeat(MyEventsListerItem.Instance, 60).ToList() // usually  there is a lot of historical data
+            };
+    }
+
+    public class MyEventsListerItem
+    {
+        public int EventId { get; set; }
+        public string EventName { get; set; }
+        public DateTimeOffset StartDate { get; set; }
+        public DateTimeOffset EndDate { get; set; }
+        public string TimeZone { get; set; }
+        public string Campaign { get; set; }
+        public string Organization { get; set; }
+        public int VolunteerCount { get; set; }
+
+        public List<MyEventsListerItemTask> Tasks { get; set; } = new List<MyEventsListerItemTask>();
+
+        public static MyEventsListerItem Instance
+            = new MyEventsListerItem
+            {
+                Campaign = "A very nice campaing",
+                EndDate = DateTime.UtcNow.AddDays(7),
+                EventId = 321,
+                EventName = "wonderful name",
+                Organization = "Local Animal Shelter",
+                StartDate = DateTime.UtcNow.AddDays(-7),
+                TimeZone = TimeZoneInfo.Utc.DisplayName,
+                VolunteerCount = 15,
+                Tasks = Enumerable.Repeat(
+                    new MyEventsListerItemTask
+                    {
+                        StartDate = DateTime.UtcNow,
+                        EndDate = DateTime.UtcNow.AddDays(1),
+                        Name = "A very nice task to have"
+                    }, 4).ToList()
+            };
+    }
+
+    public class MyEventsListerItemTask
+    {
+        public string Name { get; set; }
+        public DateTimeOffset? StartDate { get; set; }
+        public DateTimeOffset? EndDate { get; set; }
+
+        public string FormattedDate
+        {
+            get
+            {
+                if (!StartDate.HasValue || !EndDate.HasValue)
+                {
+                    return null;
+                }
+
+                var startDateString = string.Format("{0:g}", StartDate.Value);
+                var endDateString = string.Format("{0:g}", EndDate.Value);
+
+                return string.Format($"From {startDateString} to {endDateString}");
+            }
+        }
+    }
+
+    public class ClassWithNestedClass
+    {
+        public ClassWithNestedClass MyClass { get; }
+
+        public Point_2D_Struct_WithAttribute MyPoint { get; }
+
+        public ClassWithNestedClass(ClassWithNestedClass myClass, Point_2D_Struct_WithAttribute myPoint)
+        {
+            MyClass = myClass;
+            MyPoint = myPoint;
+        }
+    }
+
+    public struct StructWithFourArgs
+    {
+        public int W { get; }
+        public int X { get; }
+        public int Y { get; }
+        public int Z { get; }
+
+        [JsonConstructor]
+        public StructWithFourArgs(int w, int x, int y, int z) => (W, X, Y, Z) = (w, x, y, z);
+    }
+}
index 36926ba..66d388a 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkCurrent)</TargetFrameworks>
     <DefineConstants Condition="'$(TargetsNetFx)' != 'true'">$(DefineConstants);BUILDING_INBOX_LIBRARY</DefineConstants>
     <Compile Include="Serialization\Array.WriteTests.cs" />
     <Compile Include="Serialization\CacheTests.cs" />
     <Compile Include="Serialization\CamelCaseUnitTests.cs" />
+    <Compile Include="Serialization\ConstructorTests.AttributePresence.cs" />
+    <Compile Include="Serialization\ConstructorTests.Cache.cs" />
+    <Compile Include="Serialization\ConstructorTests.Exceptions.cs" />
+    <Compile Include="Serialization\ConstructorTests.ParameterMatching.cs" />
+    <Compile Include="Serialization\ConstructorTests.Stream.cs" />
     <Compile Include="Serialization\CustomConverterTests.Array.cs" />
     <Compile Include="Serialization\CustomConverterTests.Attribute.cs" />
     <Compile Include="Serialization\CustomConverterTests.BadConverters.cs" />
@@ -59,6 +64,7 @@
     <Compile Include="Serialization\CustomConverterTests.ReadAhead.cs" />
     <Compile Include="Serialization\CustomConverterTests.Callback.cs" />
     <Compile Include="Serialization\CyclicTests.cs" />
+    <Compile Include="Serialization\DeserializationWrapper.cs" />
     <Compile Include="Serialization\DictionaryTests.cs" />
     <Compile Include="Serialization\DictionaryTests.KeyPolicy.cs" />
     <Compile Include="Serialization\EnumConverterTests.cs" />
@@ -89,6 +95,7 @@
     <Compile Include="Serialization\TestClasses.GenericCollections.cs" />
     <Compile Include="Serialization\TestClasses.ImmutableCollections.cs" />
     <Compile Include="Serialization\TestClasses.NonGenericCollections.cs" />
+    <Compile Include="Serialization\TestClasses.Constructor.cs" />
     <Compile Include="Serialization\TestClasses.Polymorphic.cs" />
     <Compile Include="Serialization\TestClasses.SimpleTestClass.cs" />
     <Compile Include="Serialization\TestClasses.SimpleTestClassWithNullables.cs" />