Serializer perf improvements (dotnet/corefx#41238)
authorSteve Harter <steveharter@users.noreply.github.com>
Wed, 25 Sep 2019 15:46:41 +0000 (10:46 -0500)
committerGitHub <noreply@github.com>
Wed, 25 Sep 2019 15:46:41 +0000 (10:46 -0500)
Commit migrated from https://github.com/dotnet/corefx/commit/527595f14c6b3ee307d301ae42d68c29cad545d0

19 files changed:
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/JsonException.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs

index 122358e..1b44e05 100644 (file)
     <value>The attribute '{0}' cannot exist more than once on '{1}'.</value>
   </data>
   <data name="SerializeUnableToSerialize" xml:space="preserve">
-    <value>The object or value could not be serialized. Path: {0}.</value>
+    <value>The object or value could not be serialized.</value>
   </data>
   <data name="FormatByte" xml:space="preserve">
     <value>Either the JSON value is not in a supported format, or is out of bounds for an unsigned byte.</value>
index 79c1208..901f5a5 100644 (file)
@@ -94,6 +94,11 @@ namespace System.Text.Json
         }
 
         /// <summary>
+        /// Specifies that 'try' logic should append Path information to the exception message.
+        /// </summary>
+        internal bool AppendPathInformation { get; set; }
+
+        /// <summary>
         ///  Sets the <see cref="SerializationInfo"/> with information about the exception.
         /// </summary>
         /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
index b7cbf37..843d370 100644 (file)
@@ -7,20 +7,24 @@ namespace System.Text.Json
     /// <summary>
     /// Determines how a given class is treated when it is (de)serialized.
     /// </summary>
-    internal enum ClassType
+    /// <remarks>
+    /// Although bit flags are used, a given ClassType can only be one value.
+    /// Bit flags are used to efficiently compare against more than one value.
+    /// </remarks>
+    internal enum ClassType : byte
     {
         // typeof(object)
-        Unknown = 0,
+        Unknown = 0x1,
         // POCO or rich data type
-        Object = 1,
+        Object = 0x2,
         // Value or object with a converter.
-        Value = 2,
+        Value = 0x4,
         // IEnumerable
-        Enumerable = 3,
+        Enumerable = 0x8,
         // IDictionary
-        Dictionary = 4,
+        Dictionary = 0x10,
         // Is deserialized by passing a IDictionary to its constructor
         // i.e. immutable dictionaries, Hashtable, SortedList,
-        IDictionaryConstructible = 5,
+        IDictionaryConstructible = 0x20,
     }
 }
index 63672b6..59c6096 100644 (file)
@@ -22,7 +22,7 @@ namespace System.Text.Json
         private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter();
         private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter();
 
-        public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, object, object, object>();
+        public static readonly JsonPropertyInfo s_missingProperty = GetMissingProperty();
 
         private JsonClassInfo _elementClassInfo;
         private JsonClassInfo _runtimeClassInfo;
@@ -34,6 +34,15 @@ namespace System.Text.Json
 
         public abstract JsonConverter ConverterBase { get; set; }
 
+        private static JsonPropertyInfo GetMissingProperty()
+        {
+            JsonPropertyInfo info = new JsonPropertyInfoNotNullable<object, object, object, object>();
+            info.IsPropertyPolicy = false;
+            info.ShouldDeserialize = false;
+            info.ShouldSerialize = false;
+            return info;
+        }
+
         // Copy any settings defined at run-time to the new property.
         public void CopyRuntimeSettingsTo(JsonPropertyInfo other)
         {
@@ -261,6 +270,8 @@ namespace System.Text.Json
         public bool HasGetter { get; set; }
         public bool HasSetter { get; set; }
 
+        public bool HasInternalConverter { get; private set; }
+
         public virtual void Initialize(
             Type parentClassType,
             Type declaredPropertyType,
@@ -285,6 +296,8 @@ namespace System.Text.Json
             {
                 ConverterBase = converter;
 
+                HasInternalConverter = (converter.GetType().Assembly == GetType().Assembly);
+
                 // Avoid calling GetClassType since it will re-ask if there is a converter which is slow.
                 if (runtimePropertyType == typeof(object))
                 {
@@ -325,8 +338,8 @@ namespace System.Text.Json
         // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions.
         protected JsonSerializerOptions Options { get; set; }
 
-        protected abstract void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
-        protected abstract void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
+        protected abstract void OnRead(ref ReadStack state, ref Utf8JsonReader reader);
+        protected abstract void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader reader);
         protected abstract void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer);
         protected virtual void OnWriteDictionary(ref WriteStackFrame current, Utf8JsonWriter writer) { }
         protected abstract void OnWriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer);
@@ -345,15 +358,30 @@ namespace System.Text.Json
                 // Forward the setter to the value-based JsonPropertyInfo.
                 propertyInfo.ReadEnumerable(tokenType, ref state, ref reader);
             }
+            // For performance on release build, don't verify converter correctness for internal converters.
+            else if (HasInternalConverter)
+            {
+#if DEBUG
+                JsonTokenType originalTokenType = reader.TokenType;
+                int originalDepth = reader.CurrentDepth;
+                long originalBytesConsumed = reader.BytesConsumed;
+#endif
+
+                OnRead(ref state, ref reader);
+
+#if DEBUG
+                VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref reader);
+#endif
+            }
             else
             {
                 JsonTokenType originalTokenType = reader.TokenType;
                 int originalDepth = reader.CurrentDepth;
                 long originalBytesConsumed = reader.BytesConsumed;
 
-                OnRead(tokenType, ref state, ref reader);
+                OnRead(ref state, ref reader);
 
-                VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader);
+                VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref reader);
             }
         }
 
@@ -361,13 +389,31 @@ namespace System.Text.Json
         {
             Debug.Assert(ShouldDeserialize);
 
-            JsonTokenType originalTokenType = reader.TokenType;
-            int originalDepth = reader.CurrentDepth;
-            long originalBytesConsumed = reader.BytesConsumed;
+            // For performance on release build, don't verify converter correctness for internal converters.
+            if (HasInternalConverter)
+            {
+#if DEBUG
+                JsonTokenType originalTokenType = reader.TokenType;
+                int originalDepth = reader.CurrentDepth;
+                long originalBytesConsumed = reader.BytesConsumed;
+#endif
+
+                OnReadEnumerable(ref state, ref reader);
 
-            OnReadEnumerable(tokenType, ref state, ref reader);
+#if DEBUG
+                VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref reader);
+#endif
+            }
+            else
+            {
+                JsonTokenType originalTokenType = reader.TokenType;
+                int originalDepth = reader.CurrentDepth;
+                long originalBytesConsumed = reader.BytesConsumed;
 
-            VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader);
+                OnReadEnumerable(ref state, ref reader);
+
+                VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref reader);
+            }
         }
 
         public JsonClassInfo RuntimeClassInfo
@@ -403,18 +449,18 @@ namespace System.Text.Json
         public bool ShouldSerialize { get; private set; }
         public bool ShouldDeserialize { get; private set; }
 
-        private void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, ref ReadStack state, ref Utf8JsonReader reader)
+        private void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, ref Utf8JsonReader reader)
         {
             switch (tokenType)
             {
                 case JsonTokenType.StartArray:
                     if (reader.TokenType != JsonTokenType.EndArray)
                     {
-                        ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath(), ConverterBase.ToString());
+                        ThrowHelper.ThrowJsonException_SerializationConverterRead(ConverterBase);
                     }
                     else if (depth != reader.CurrentDepth)
                     {
-                        ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath(), ConverterBase.ToString());
+                        ThrowHelper.ThrowJsonException_SerializationConverterRead(ConverterBase);
                     }
 
                     // Should not be possible to have not read anything.
@@ -424,11 +470,11 @@ namespace System.Text.Json
                 case JsonTokenType.StartObject:
                     if (reader.TokenType != JsonTokenType.EndObject)
                     {
-                        ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath(), ConverterBase.ToString());
+                        ThrowHelper.ThrowJsonException_SerializationConverterRead(ConverterBase);
                     }
                     else if (depth != reader.CurrentDepth)
                     {
-                        ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath(), ConverterBase.ToString());
+                        ThrowHelper.ThrowJsonException_SerializationConverterRead(ConverterBase);
                     }
 
                     // Should not be possible to have not read anything.
@@ -439,7 +485,7 @@ namespace System.Text.Json
                     // Reading a single property value.
                     if (reader.BytesConsumed != bytesConsumed)
                     {
-                        ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath(), ConverterBase.ToString());
+                        ThrowHelper.ThrowJsonException_SerializationConverterRead(ConverterBase);
                     }
 
                     // Should not be possible to change token type.
@@ -459,42 +505,82 @@ namespace System.Text.Json
                 JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty;
                 propertyInfo.WriteEnumerable(ref state, writer);
             }
-            else
+            // For performance on release build, don't verify converter correctness for internal converters.
+            else if (HasInternalConverter)
             {
+#if DEBUG
                 int originalDepth = writer.CurrentDepth;
+#endif
 
                 OnWrite(ref state.Current, writer);
 
-                if (originalDepth != writer.CurrentDepth)
-                {
-                    ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath(), ConverterBase.ToString());
-                }
+#if DEBUG
+                VerifyWrite(originalDepth, writer);
+#endif
+            }
+            else
+            {
+                int originalDepth = writer.CurrentDepth;
+                OnWrite(ref state.Current, writer);
+                VerifyWrite(originalDepth, writer);
             }
         }
 
         public void WriteDictionary(ref WriteStack state, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
-            int originalDepth = writer.CurrentDepth;
 
-            OnWriteDictionary(ref state.Current, writer);
+            // For performance on release build, don't verify converter correctness for internal converters.
+            if (HasInternalConverter)
+            {
+#if DEBUG
+                int originalDepth = writer.CurrentDepth;
+#endif
 
-            if (originalDepth != writer.CurrentDepth)
+                OnWriteDictionary(ref state.Current, writer);
+
+#if DEBUG
+                VerifyWrite(originalDepth, writer);
+#endif
+            }
+            else
             {
-                ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath(), ConverterBase.ToString());
+                int originalDepth = writer.CurrentDepth;
+                OnWriteDictionary(ref state.Current, writer);
+                VerifyWrite(originalDepth, writer);
             }
         }
 
         public void WriteEnumerable(ref WriteStack state, Utf8JsonWriter writer)
         {
             Debug.Assert(ShouldSerialize);
-            int originalDepth = writer.CurrentDepth;
 
-            OnWriteEnumerable(ref state.Current, writer);
+            // For performance on release build, don't verify converter correctness for internal converters.
+            if (HasInternalConverter)
+            {
+#if DEBUG
+                int originalDepth = writer.CurrentDepth;
+#endif
+
+                OnWriteEnumerable(ref state.Current, writer);
+
+#if DEBUG
+                VerifyWrite(originalDepth, writer);
+#endif
+            }
+            else
+            {
+                int originalDepth = writer.CurrentDepth;
+                OnWriteEnumerable(ref state.Current, writer);
+                VerifyWrite(originalDepth, writer);
+            }
+        }
 
+        private void VerifyWrite(int originalDepth, Utf8JsonWriter writer)
+        {
             if (originalDepth != writer.CurrentDepth)
             {
-                ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath(), ConverterBase.ToString());
+                ThrowHelper.ThrowJsonException_SerializationConverterWrite(ConverterBase);
             }
         }
     }
index 6a0538b..eb90e96 100644 (file)
@@ -4,7 +4,6 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Text.Json.Serialization;
 
 namespace System.Text.Json
 {
@@ -15,11 +14,11 @@ namespace System.Text.Json
         JsonPropertyInfoCommon<TClass, TDeclaredProperty, TRuntimeProperty, TConverter>
         where TConverter : TDeclaredProperty
     {
-        protected override void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnRead(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
             TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options);
@@ -34,28 +33,28 @@ namespace System.Text.Json
             }
         }
 
-        protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
-            if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible))
+            if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
             }
 
             // We need an initialized array in order to store the values.
-            if (state.Current.IsProcessingEnumerable && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null)
+            if (state.Current.IsProcessingEnumerable() && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
             }
 
             TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options);
-            JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader);
+            JsonSerializer.ApplyValueToEnumerable(ref value, ref state);
         }
 
         protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer)
index cc37701..4216239 100644 (file)
@@ -14,11 +14,11 @@ namespace System.Text.Json.Serialization
         JsonPropertyInfoCommon<TClass, TDeclaredProperty, TRuntimeProperty, TConverter>
         where TDeclaredProperty : TConverter
     {
-        protected override void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnRead(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
             TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options);
@@ -35,28 +35,28 @@ namespace System.Text.Json.Serialization
             return;
         }
 
-        protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
-            if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible))
+            if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
             }
 
             // We need an initialized array in order to store the values.
-            if (state.Current.IsProcessingEnumerable && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null)
+            if (state.Current.IsProcessingEnumerable() && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
                 return;
             }
 
             TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options);
-            JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader);
+            JsonSerializer.ApplyValueToEnumerable(ref value, ref state);
         }
 
         protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer)
index 431df44..724578e 100644 (file)
@@ -5,7 +5,6 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Text.Json.Serialization;
 
 namespace System.Text.Json
 {
@@ -18,11 +17,11 @@ namespace System.Text.Json
     {
         private static readonly Type s_underlyingType = typeof(TProperty);
 
-        protected override void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnRead(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
             TProperty value = Converter.Read(ref reader, s_underlyingType, Options);
@@ -37,16 +36,16 @@ namespace System.Text.Json
             }
         }
 
-        protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader)
+        protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader reader)
         {
             if (Converter == null)
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
             }
 
             TProperty value = Converter.Read(ref reader, s_underlyingType, Options);
             TProperty? nullableValue = new TProperty?(value);
-            JsonSerializer.ApplyValueToEnumerable(ref nullableValue, ref state, ref reader);
+            JsonSerializer.ApplyValueToEnumerable(ref nullableValue, ref state);
         }
 
         protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer)
index 1e69c64..e845153 100644 (file)
@@ -38,10 +38,10 @@ namespace System.Text.Json
             Type arrayType = jsonPropertyInfo.RuntimePropertyType;
             if (!typeof(IEnumerable).IsAssignableFrom(arrayType))
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType);
             }
 
-            Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary);
+            Debug.Assert(state.Current.IsProcessingCollection());
 
             if (state.Current.CollectionPropertyInitialized)
             {
@@ -54,35 +54,29 @@ namespace System.Text.Json
 
             state.Current.CollectionPropertyInitialized = true;
 
-            if (state.Current.JsonClassInfo.ClassType == ClassType.Value)
-            {
-                // Custom converter code path.
-                state.Current.JsonPropertyInfo.Read(JsonTokenType.StartObject, ref state, ref reader);
-            }
-            else
-            {
-                // Set or replace the existing enumerable value.
-                object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state);
+            // We should not be processing custom converters here.
+            Debug.Assert(state.Current.JsonClassInfo.ClassType != ClassType.Value);
+
+            // Set or replace the existing enumerable value.
+            object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state);
 
-                // If value is not null, then we don't have a converter so apply the value.
-                if (value != null)
+            // If value is not null, then we don't have a converter so apply the value.
+            if (value != null)
+            {
+                if (state.Current.ReturnValue != null)
                 {
-                    if (state.Current.ReturnValue != null)
-                    {
-                        state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
-                    }
-                    else
-                    {
-                        // Primitive arrays being returned without object
-                        state.Current.SetReturnValue(value);
-                    }
+                    state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+                }
+                else
+                {
+                    // Primitive arrays being returned without object
+                    state.Current.SetReturnValue(value);
                 }
             }
         }
 
         private static bool HandleEndArray(
             JsonSerializerOptions options,
-            ref Utf8JsonReader reader,
             ref ReadStack state)
         {
             bool lastFrame = state.IsLastFrame;
@@ -110,7 +104,7 @@ namespace System.Text.Json
                 value = converter.CreateFromList(ref state, (IList)value, options);
                 state.Current.TempEnumerableValues = null;
             }
-            else if (state.Current.IsEnumerableProperty)
+            else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
             {
                 // We added the items to the list already.
                 state.Current.EndProperty();
@@ -126,19 +120,19 @@ namespace System.Text.Json
                     state.Current.ReturnValue = value;
                     return true;
                 }
-                else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
+                else if (state.Current.IsProcessingCollectionObject())
                 {
                     // Returning a non-converted list.
                     return true;
                 }
                 // else there must be an outer object, so we'll return false here.
             }
-            else if (state.Current.IsEnumerable)
+            else if (state.Current.IsProcessingObject(ClassType.Enumerable))
             {
                 state.Pop();
             }
 
-            ApplyObjectToEnumerable(value, ref state, ref reader);
+            ApplyObjectToEnumerable(value, ref state);
 
             return false;
         }
@@ -147,12 +141,11 @@ namespace System.Text.Json
         internal static void ApplyObjectToEnumerable(
             object value,
             ref ReadStack state,
-            ref Utf8JsonReader reader,
             bool setPropertyDirectly = false)
         {
             Debug.Assert(!state.Current.SkipProperty);
 
-            if (state.Current.IsEnumerable)
+            if (state.Current.IsProcessingObject(ClassType.Enumerable))
             {
                 if (state.Current.TempEnumerableValues != null)
                 {
@@ -162,13 +155,13 @@ namespace System.Text.Json
                 {
                     if (!(state.Current.ReturnValue is IList list))
                     {
-                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath());
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
                         return;
                     }
                     list.Add(value);
                 }
             }
-            else if (!setPropertyDirectly && state.Current.IsEnumerableProperty)
+            else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable))
             {
                 Debug.Assert(state.Current.JsonPropertyInfo != null);
                 Debug.Assert(state.Current.ReturnValue != null);
@@ -191,7 +184,8 @@ namespace System.Text.Json
                     }
                 }
             }
-            else if (state.Current.IsDictionary || (state.Current.IsDictionaryProperty && !setPropertyDirectly))
+            else if (state.Current.IsProcessingObject(ClassType.Dictionary) ||
+                (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
             {
                 Debug.Assert(state.Current.ReturnValue != null);
                 IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
@@ -200,8 +194,8 @@ namespace System.Text.Json
                 Debug.Assert(!string.IsNullOrEmpty(key));
                 dictionary[key] = value;
             }
-            else if (state.Current.IsIDictionaryConstructible ||
-                (state.Current.IsIDictionaryConstructibleProperty && !setPropertyDirectly))
+            else if (state.Current.IsProcessingObject(ClassType.IDictionaryConstructible) ||
+                (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly))
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
                 IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues;
@@ -220,12 +214,11 @@ namespace System.Text.Json
         // If this method is changed, also change ApplyObjectToEnumerable.
         internal static void ApplyValueToEnumerable<TProperty>(
             ref TProperty value,
-            ref ReadStack state,
-            ref Utf8JsonReader reader)
+            ref ReadStack state)
         {
             Debug.Assert(!state.Current.SkipProperty);
 
-            if (state.Current.IsEnumerable)
+            if (state.Current.IsProcessingObject(ClassType.Enumerable))
             {
                 if (state.Current.TempEnumerableValues != null)
                 {
@@ -236,7 +229,7 @@ namespace System.Text.Json
                     ((IList<TProperty>)state.Current.ReturnValue).Add(value);
                 }
             }
-            else if (state.Current.IsEnumerableProperty)
+            else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
             {
                 Debug.Assert(state.Current.JsonPropertyInfo != null);
                 Debug.Assert(state.Current.ReturnValue != null);
@@ -257,7 +250,7 @@ namespace System.Text.Json
                     }
                 }
             }
-            else if (state.Current.IsProcessingDictionary)
+            else if (state.Current.IsProcessingDictionary())
             {
                 Debug.Assert(state.Current.ReturnValue != null);
                 IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
@@ -266,7 +259,7 @@ namespace System.Text.Json
                 Debug.Assert(!string.IsNullOrEmpty(key));
                 dictionary[key] = value;
             }
-            else if (state.Current.IsProcessingIDictionaryConstructible)
+            else if (state.Current.IsProcessingIDictionaryConstructible())
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
                 IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.TempDictionaryValues;
index b4ed98c..f434ed4 100644 (file)
@@ -10,9 +10,9 @@ namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
+        private static void HandleStartDictionary(JsonSerializerOptions options, ref ReadStack state)
         {
-            Debug.Assert(!state.Current.IsProcessingEnumerable);
+            Debug.Assert(!state.Current.IsProcessingEnumerable());
 
             JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
             if (jsonPropertyInfo == null)
@@ -35,12 +35,12 @@ namespace System.Text.Json
                     jsonPropertyInfo.ElementClassInfo.Type != typeof(object) &&
                     jsonPropertyInfo.ElementClassInfo.Type != typeof(JsonElement))
                 {
-                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.JsonPath());
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type);
                 }
 
                 JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-                if (state.Current.IsProcessingIDictionaryConstructible)
+                if (state.Current.IsProcessingIDictionaryConstructible())
                 {
                     state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
                 }
@@ -48,7 +48,7 @@ namespace System.Text.Json
                 {
                     if (classInfo.CreateObject == null)
                     {
-                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(classInfo.Type, reader, state.JsonPath());
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(classInfo.Type);
                         return;
                     }
                     state.Current.ReturnValue = classInfo.CreateObject();
@@ -59,7 +59,7 @@ namespace System.Text.Json
 
             state.Current.CollectionPropertyInitialized = true;
 
-            if (state.Current.IsProcessingIDictionaryConstructible)
+            if (state.Current.IsProcessingIDictionaryConstructible())
             {
                 JsonClassInfo dictionaryClassInfo;
                 if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType)
@@ -94,22 +94,18 @@ namespace System.Text.Json
             }
         }
 
-        private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
+        private static void HandleEndDictionary(JsonSerializerOptions options, ref ReadStack state)
         {
-            if (state.Current.SkipProperty)
-            {
-                // Todo: determine if this is reachable.
-                return;
-            }
+            Debug.Assert(!state.Current.SkipProperty);
 
-            if (state.Current.IsDictionaryProperty)
+            if (state.Current.IsProcessingProperty(ClassType.Dictionary))
             {
                 // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property.
                 // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject
                 // encountered here is from the outer object so forward to HandleEndObject().
                 if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo)
                 {
-                    HandleEndObject(ref reader, ref state);
+                    HandleEndObject(ref state);
                 }
                 else
                 {
@@ -117,7 +113,7 @@ namespace System.Text.Json
                     state.Current.EndProperty();
                 }
             }
-            else if (state.Current.IsIDictionaryConstructibleProperty)
+            else if (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible))
             {
                 Debug.Assert(state.Current.TempDictionaryValues != null);
                 JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
@@ -146,7 +142,7 @@ namespace System.Text.Json
                 else
                 {
                     state.Pop();
-                    ApplyObjectToEnumerable(value, ref state, ref reader);
+                    ApplyObjectToEnumerable(value, ref state);
                 }
             }
         }
index da7f315..fd6c9d8 100644 (file)
@@ -30,13 +30,13 @@ namespace System.Text.Json
 
             Debug.Assert(jsonPropertyInfo != null);
 
-            if (state.Current.IsCollectionForClass)
+            if (state.Current.IsProcessingCollectionObject())
             {
                 AddNullToCollection(jsonPropertyInfo, ref reader, ref state);
                 return false;
             }
 
-            if (state.Current.IsCollectionForProperty)
+            if (state.Current.IsProcessingCollectionProperty())
             {
                 if (state.Current.CollectionPropertyInitialized)
                 {
@@ -46,7 +46,7 @@ namespace System.Text.Json
                 else
                 {
                     // Set the property to null.
-                    ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: true);
+                    ApplyObjectToEnumerable(null, ref state, setPropertyDirectly: true);
 
                     // Reset so that `Is*Property` no longer returns true
                     state.Current.EndProperty();
@@ -92,7 +92,7 @@ namespace System.Text.Json
             else
             {
                 // Assume collection types are reference types and can have null assigned.
-                ApplyObjectToEnumerable(null, ref state, ref reader);
+                ApplyObjectToEnumerable(null, ref state);
             }
         }
     }
index 7a3ac14..c0b551c 100644 (file)
@@ -11,9 +11,9 @@ namespace System.Text.Json
     {
         private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state)
         {
-            Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructible);
+            Debug.Assert(!state.Current.IsProcessingDictionaryOrIDictionaryConstructible());
 
-            if (state.Current.IsProcessingEnumerable)
+            if (state.Current.IsProcessingEnumerable())
             {
                 // A nested object within an enumerable.
                 Type objType = state.Current.GetElementType();
@@ -42,7 +42,7 @@ namespace System.Text.Json
                 }
             }
 
-            if (state.Current.IsProcessingIDictionaryConstructible)
+            if (state.Current.IsProcessingIDictionaryConstructible())
             {
                 state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
             }
@@ -52,12 +52,12 @@ namespace System.Text.Json
             }
         }
 
-        private static void HandleEndObject(ref Utf8JsonReader reader, ref ReadStack state)
+        private static void HandleEndObject(ref ReadStack state)
         {
             // Only allow dictionaries to be processed here if this is the DataExtensionProperty.
             Debug.Assert(
-                (!state.Current.IsProcessingDictionary || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) &&
-                !state.Current.IsProcessingIDictionaryConstructible);
+                (!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) &&
+                !state.Current.IsProcessingIDictionaryConstructible());
 
             // Check if we are trying to build the sorted cache.
             if (state.Current.PropertyRefCache != null)
@@ -75,7 +75,7 @@ namespace System.Text.Json
             else
             {
                 state.Pop();
-                ApplyObjectToEnumerable(value, ref state, ref reader);
+                ApplyObjectToEnumerable(value, ref state);
             }
         }
     }
index 92cf2b8..a6fee2b 100644 (file)
@@ -26,20 +26,14 @@ namespace System.Text.Json
             Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default);
             Debug.Assert(state.Current.JsonClassInfo != default);
 
-            if ((state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible) &&
+            if (state.Current.IsProcessingDictionaryOrIDictionaryConstructible() &&
                 state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo)
             {
-                if (state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
+                if (state.Current.IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible))
                 {
                     state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty;
                 }
 
-                Debug.Assert(
-                    state.Current.IsDictionary ||
-                    (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null) ||
-                    state.Current.IsIDictionaryConstructible ||
-                    (state.Current.IsIDictionaryConstructibleProperty && state.Current.JsonPropertyInfo != null));
-
                 state.Current.KeyName = reader.GetString();
             }
             else
index 420a9f9..0e262fe 100644 (file)
@@ -2,10 +2,17 @@
 // 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
 {
     public static partial class JsonSerializer
     {
+        // The maximum length of a byte array before we ask for the actual transcoded byte size.
+        // The "int.MaxValue / 2" is used because max byte[] length is a bit less than int.MaxValue
+        // and because we don't want to allocate such a large buffer if not necessary.
+        private const int MaxArrayLengthBeforeCalculatingSize = int.MaxValue / 2 / JsonConstants.MaxExpansionFactorWhileTranscoding;
+
         /// <summary>
         /// Parse the text representing a single JSON value into a <typeparamref name="TValue"/>.
         /// </summary>
@@ -25,10 +32,7 @@ namespace System.Text.Json
         /// </remarks>
         public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null)
         {
-            if (json == null)
-                throw new ArgumentNullException(nameof(json));
-
-            return (TValue)ParseCore(json, typeof(TValue), options);
+            return (TValue)Deserialize(json, typeof(TValue), options);
         }
 
         /// <summary>
@@ -52,31 +56,62 @@ namespace System.Text.Json
         public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null)
         {
             if (json == null)
+            {
                 throw new ArgumentNullException(nameof(json));
+            }
 
             if (returnType == null)
+            {
                 throw new ArgumentNullException(nameof(returnType));
+            }
 
-            return ParseCore(json, returnType, options);
-        }
-
-        private static object ParseCore(string json, Type returnType, JsonSerializerOptions options = null)
-        {
             if (options == null)
             {
                 options = JsonSerializerOptions.s_defaultOptions;
             }
 
-            // todo: use an array pool here for smaller requests to avoid the alloc?
-            byte[] jsonBytes = JsonReaderHelper.s_utf8Encoding.GetBytes(json);
-            var readerState = new JsonReaderState(options.GetReaderOptions());
-            var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, readerState);
-            object result = ReadCore(returnType, options, ref reader);
+            object result;
+            byte[] tempArray = null;
 
-            if (reader.BytesConsumed != jsonBytes.Length)
+            int maxBytes;
+
+            // For performance, avoid asking for the actual byte count unless necessary.
+            if (json.Length > MaxArrayLengthBeforeCalculatingSize)
+            {
+                // Get the actual byte count in order to handle large input.
+                maxBytes = JsonReaderHelper.GetUtf8ByteCount(json.AsSpan());
+            }
+            else
+            {
+                maxBytes = json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding;
+            }
+
+            Span<byte> utf8 = maxBytes <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[maxBytes] :
+                (tempArray = ArrayPool<byte>.Shared.Rent(maxBytes));
+
+            try
+            {
+                int actualByteCount = JsonReaderHelper.GetUtf8FromText(json.AsSpan(), utf8);
+                utf8 = utf8.Slice(0, actualByteCount);
+
+                var readerState = new JsonReaderState(options.GetReaderOptions());
+                var reader = new Utf8JsonReader(utf8, isFinalBlock: true, readerState);
+                result = ReadCore(returnType, options, ref reader);
+
+                if (reader.BytesConsumed != actualByteCount)
+                {
+                    ThrowHelper.ThrowJsonException_DeserializeDataRemaining(
+                        actualByteCount, actualByteCount - reader.BytesConsumed);
+                }
+            }
+            finally
             {
-                ThrowHelper.ThrowJsonException_DeserializeDataRemaining(
-                    jsonBytes.Length, jsonBytes.Length - reader.BytesConsumed);
+                if (tempArray != null)
+                {
+                    utf8.Clear();
+                    ArrayPool<byte>.Shared.Return(tempArray);
+                }
             }
 
             return result;
index 841568c..5ff0073 100644 (file)
@@ -68,9 +68,9 @@ namespace System.Text.Json
                                 break;
                             }
                         }
-                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible)
+                        else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
                         {
-                            HandleStartDictionary(options, ref reader, ref readStack);
+                            HandleStartDictionary(options, ref readStack);
                         }
                         else
                         {
@@ -87,13 +87,13 @@ namespace System.Text.Json
                             // A non-dictionary property can also have EndProperty() called when completed, although it is redundant.
                             readStack.Current.EndProperty();
                         }
-                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible)
+                        else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
                         {
-                            HandleEndDictionary(options, ref reader, ref readStack);
+                            HandleEndDictionary(options, ref readStack);
                         }
                         else
                         {
-                            HandleEndObject(ref reader, ref readStack);
+                            HandleEndObject(ref readStack);
                         }
                     }
                     else if (tokenType == JsonTokenType.StartArray)
@@ -110,7 +110,7 @@ namespace System.Text.Json
                     }
                     else if (tokenType == JsonTokenType.EndArray)
                     {
-                        HandleEndArray(options, ref reader, ref readStack);
+                        HandleEndArray(options, ref readStack);
                     }
                     else if (tokenType == JsonTokenType.Null)
                     {
@@ -138,7 +138,6 @@ namespace System.Text.Json
             }
 
             readStack.BytesConsumed += reader.BytesConsumed;
-            return;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
index 5d40d26..b0effbf 100644 (file)
@@ -9,8 +9,8 @@ namespace System.Text.Json
     public static partial class JsonSerializer
     {
         // There are three conditions to consider for an object (primitive value, enumerable or object) being processed here:
-        // 1) The object type was specified as the root-level return type to a Parse\Read method.
-        // 2) The object is property on a parent object.
+        // 1) The object type was specified as the root-level return type to a Deserialize method.
+        // 2) The object is property on a parent object.
         // 3) The object is an element in an enumerable.
         private static bool Write(
             Utf8JsonWriter writer,
@@ -25,20 +25,19 @@ namespace System.Text.Json
             {
                 do
                 {
-                    WriteStackFrame current = state.Current;
-                    switch (current.JsonClassInfo.ClassType)
+                    switch (state.Current.JsonClassInfo.ClassType)
                     {
                         case ClassType.Enumerable:
-                            finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
+                            finishedSerializing = HandleEnumerable(state.Current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
                             break;
                         case ClassType.Value:
-                            Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value);
-                            current.JsonPropertyInfo.Write(ref state, writer);
+                            Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Value);
+                            state.Current.JsonPropertyInfo.Write(ref state, writer);
                             finishedSerializing = true;
                             break;
                         case ClassType.Dictionary:
                         case ClassType.IDictionaryConstructible:
-                            finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
+                            finishedSerializing = HandleDictionary(state.Current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
                             break;
                         default:
                             Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object ||
index 93ab7a1..41c1d0b 100644 (file)
@@ -75,12 +75,12 @@ namespace System.Text.Json
 
             if (frame.JsonClassInfo != null)
             {
-                if (frame.IsProcessingDictionary)
+                if (frame.IsProcessingDictionary())
                 {
                     // For dictionaries add the key.
                     AppendPropertyName(sb, frame.KeyName);
                 }
-                else if (frame.IsProcessingEnumerable)
+                else if (frame.IsProcessingEnumerable())
                 {
                     // For enumerables add the index.
                     IList list = frame.TempEnumerableValues;
index b3f43d1..accb266 100644 (file)
@@ -32,6 +32,9 @@ namespace System.Text.Json
         // Has an array or dictionary property been initialized.
         public bool CollectionPropertyInitialized;
 
+        // The current JSON data for a property does not match a given POCO, so ignore the property (recursively).
+        public bool Drain;
+
         // Support IDictionary constructible types, i.e. types that we
         // support by passing and IDictionary to their constructors:
         // immutable dictionaries, Hashtable, SortedList
@@ -41,35 +44,91 @@ namespace System.Text.Json
         public int PropertyIndex;
         public List<PropertyRef> PropertyRefCache;
 
-        // The current JSON data for a property does not match a given POCO, so ignore the property (recursively).
-        public bool Drain;
+        /// <summary>
+        /// Is the current object an Enumerable, Dictionary or IDictionaryConstructible.
+        /// </summary>
+        public bool IsProcessingCollectionObject()
+        {
+            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+        }
 
-        public bool IsCollectionForClass => IsEnumerable || IsDictionary || IsIDictionaryConstructible;
-        public bool IsCollectionForProperty => IsEnumerableProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty;
+        /// <summary>
+        /// Is the current property an Enumerable, Dictionary or IDictionaryConstructible.
+        /// </summary>
+        public bool IsProcessingCollectionProperty()
+        {
+            return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+        }
 
-        public bool IsIDictionaryConstructible => JsonClassInfo.ClassType == ClassType.IDictionaryConstructible;
-        public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary;
+        /// <summary>
+        /// Is the current object or property an Enumerable, Dictionary or IDictionaryConstructible.
+        /// </summary>
+        public bool IsProcessingCollection()
+        {
+            return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
+                IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+        }
 
-        public bool IsDictionaryProperty => JsonPropertyInfo != null &&
-            !JsonPropertyInfo.IsPropertyPolicy &&
-            JsonPropertyInfo.ClassType == ClassType.Dictionary;
-        public bool IsIDictionaryConstructibleProperty => JsonPropertyInfo != null &&
-            !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible);
+        /// <summary>
+        /// Is the current object or property a Dictionary.
+        /// </summary>
+        public bool IsProcessingDictionary()
+        {
+            return IsProcessingObject(ClassType.Dictionary) ||
+                IsProcessingProperty(ClassType.Dictionary);
+        }
 
-        public bool IsEnumerable => JsonClassInfo.ClassType == ClassType.Enumerable;
+        /// <summary>
+        /// Is the current object or property an IDictionaryConstructible.
+        /// </summary>
+        public bool IsProcessingIDictionaryConstructible()
+        {
+            return IsProcessingObject(ClassType.IDictionaryConstructible)
+                || IsProcessingProperty(ClassType.IDictionaryConstructible);
+        }
 
-        public bool IsEnumerableProperty =>
-            JsonPropertyInfo != null &&
-            !JsonPropertyInfo.IsPropertyPolicy &&
-            JsonPropertyInfo.ClassType == ClassType.Enumerable;
+        /// <summary>
+        /// Is the current object or property a Dictionary or IDictionaryConstructible.
+        /// </summary>
+        public bool IsProcessingDictionaryOrIDictionaryConstructible()
+        {
+            return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
+                IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible);
+        }
 
-        public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary || IsProcessingIDictionaryConstructible;
-        public bool IsProcessingDictionary => IsDictionary || IsDictionaryProperty;
-        public bool IsProcessingIDictionaryConstructible => IsIDictionaryConstructible || IsIDictionaryConstructibleProperty;
-        public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty;
+        /// <summary>
+        /// Is the current object or property an Enumerable.
+        /// </summary>
+        public bool IsProcessingEnumerable()
+        {
+            return IsProcessingObject(ClassType.Enumerable) ||
+                IsProcessingProperty(ClassType.Enumerable);
+        }
 
+        /// <summary>
+        /// Is the current object of the provided <paramref name="classTypes"/>.
+        /// </summary>
+        public bool IsProcessingObject(ClassType classTypes)
+        {
+            return (JsonClassInfo.ClassType & classTypes) != 0;
+        }
+
+        /// <summary>
+        /// Is the current property of the provided <paramref name="classTypes"/>.
+        /// </summary>
+        public bool IsProcessingProperty(ClassType classTypes)
+        {
+            return JsonPropertyInfo != null &&
+                !JsonPropertyInfo.IsPropertyPolicy &&
+                (JsonPropertyInfo.ClassType & classTypes) != 0;
+        }
+
+        /// <summary>
+        /// Determine whether a StartObject or StartArray token should be treated as a value.
+        /// This allows read-ahead functionality required for Streams so that a custom converter
+        /// does not run out of data and fail.
+        /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        // Determine whether a StartObject or StartArray token should be treated as a value.
         public bool IsProcessingValue()
         {
             if (SkipProperty)
@@ -92,7 +151,11 @@ namespace System.Text.Json
                 classType = JsonPropertyInfo.ClassType;
             }
 
-            return classType == ClassType.Value || classType == ClassType.Unknown;
+            // A ClassType.Value indicates the object has a converter and ClassType.Unknown indicates the
+            // property or element type is System.Object.
+            // System.Object is treated as a JsonElement which requires returning true from this
+            // method in order to properly read-ahead (since JsonElement has a custom converter).
+            return (classType & (ClassType.Value | ClassType.Unknown)) != 0;
         }
 
         public void Initialize(Type type, JsonSerializerOptions options)
@@ -103,10 +166,7 @@ namespace System.Text.Json
 
         public void InitializeJsonPropertyInfo()
         {
-            if (JsonClassInfo.ClassType == ClassType.Value ||
-                JsonClassInfo.ClassType == ClassType.Enumerable ||
-                JsonClassInfo.ClassType == ClassType.Dictionary ||
-                JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
+            if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible))
             {
                 JsonPropertyInfo = JsonClassInfo.PolicyProperty;
             }
@@ -187,25 +247,25 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.DeclaredPropertyType, reader, state.JsonPath());
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.DeclaredPropertyType);
                     return null;
                 }
             }
             else
             {
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType, reader, state.JsonPath());
+                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType);
                 return null;
             }
         }
 
         public Type GetElementType()
         {
-            if (IsCollectionForProperty)
+            if (IsProcessingCollectionProperty())
             {
                 return JsonPropertyInfo.ElementClassInfo.Type;
             }
 
-            if (IsCollectionForClass)
+            if (IsProcessingCollectionObject())
             {
                 return JsonClassInfo.ElementClassInfo.Type;
             }
@@ -215,7 +275,7 @@ namespace System.Text.Json
 
         public static IEnumerable GetEnumerableValue(ref ReadStackFrame current)
         {
-            if (current.IsEnumerable)
+            if (current.IsProcessingObject(ClassType.Enumerable))
             {
                 if (current.ReturnValue != null)
                 {
@@ -234,7 +294,8 @@ namespace System.Text.Json
         }
 
         public bool SkipProperty => Drain ||
-            ReferenceEquals(JsonPropertyInfo, JsonPropertyInfo.s_missingProperty) ||
-            (JsonPropertyInfo?.IsPropertyPolicy == false && JsonPropertyInfo?.ShouldDeserialize == false);
+            JsonPropertyInfo != null &&
+            JsonPropertyInfo.IsPropertyPolicy == false &&
+            JsonPropertyInfo.ShouldDeserialize == false;
     }
 }
index 1b08fd7..e39b8fd 100644 (file)
@@ -34,9 +34,11 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, in Utf8JsonReader reader, string path, Exception innerException = null)
+        public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
         {
-            ThrowJsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType), in reader, path, innerException);
+            var ex = new JsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType));
+            ex.AppendPathInformation = true;
+            throw ex;
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
@@ -47,23 +49,25 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_DepthTooLarge(int currentDepth, in WriteStack writeStack, JsonSerializerOptions options)
+        public static void ThrowJsonException_DepthTooLarge(int currentDepth, JsonSerializerOptions options)
         {
-            var ex = new JsonException(SR.Format(SR.DepthTooLarge, currentDepth, options.EffectiveMaxDepth));
-            AddExceptionInformation(writeStack, ex);
-            throw ex;
+            throw new JsonException(SR.Format(SR.DepthTooLarge, currentDepth, options.EffectiveMaxDepth));
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_SerializationConverterRead(in Utf8JsonReader reader, string path, string converter)
+        public static void ThrowJsonException_SerializationConverterRead(JsonConverter converter)
         {
-            ThrowJsonException(SR.Format(SR.SerializationConverterRead, converter), reader, path);
+            var ex = new JsonException(SR.Format(SR.SerializationConverterRead, converter));
+            ex.AppendPathInformation = true;
+            throw ex;
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_SerializationConverterWrite(string path, string converter)
+        public static void ThrowJsonException_SerializationConverterWrite(JsonConverter converter)
         {
-            ThrowJsonException(SR.Format(SR.SerializationConverterWrite, converter), path);
+            var ex = new JsonException(SR.Format(SR.SerializationConverterWrite, converter));
+            ex.AppendPathInformation = true;
+            throw ex;
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
@@ -132,22 +136,6 @@ namespace System.Text.Json
             throw new JsonException(SR.Format(SR.DeserializeDataRemaining, length, bytesRemaining), path: null, lineNumber: null, bytePositionInLine: null);
         }
 
-        // todo: since we now catch and re-throw JsonException and add Path etc, we can clean up callers to this to not pass the reader and path.
-        private static void ThrowJsonException(string message, in Utf8JsonReader reader, string path, Exception innerException = null)
-        {
-            long lineNumber = reader.CurrentState._lineNumber;
-            long bytePositionInLine = reader.CurrentState._bytePositionInLine;
-
-            message += $" Path: {path} | LineNumber: {lineNumber} | BytePositionInLine: {bytePositionInLine}.";
-            throw new JsonException(message, path, lineNumber, bytePositionInLine, innerException);
-        }
-
-        private static void ThrowJsonException(string message, string path, Exception innerException = null)
-        {
-            message += $" Path: {path}.";
-            throw new JsonException(message, path, null, null, innerException);
-        }
-
         [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ReThrowWithPath(in ReadStack readStack, JsonReaderException ex)
         {
@@ -189,16 +177,26 @@ namespace System.Text.Json
             string path = readStack.JsonPath();
             ex.Path = path;
 
-            // If the message is empty, use a default message with Path, LineNumber and BytePositionInLine.
-            if (string.IsNullOrEmpty(ex.Message))
+            string message = ex.Message;
+
+            // If the message is empty or contains EmbedPathInfoFlag then append the Path, LineNumber and BytePositionInLine.
+            if (string.IsNullOrEmpty(message))
             {
+                // Use a default message.
                 Type propertyType = readStack.Current.JsonPropertyInfo?.RuntimePropertyType;
                 if (propertyType == null)
                 {
                     propertyType = readStack.Current.JsonClassInfo.Type;
                 }
 
-                ex.SetMessage($"{SR.Format(SR.DeserializeUnableToConvertValue, propertyType)} Path: {path} | LineNumber: {lineNumber} | BytePositionInLine: {bytePositionInLine}.");
+                message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType);
+                ex.AppendPathInformation = true;
+            }
+
+            if (ex.AppendPathInformation)
+            {
+                message += $" Path: {path} | LineNumber: {lineNumber} | BytePositionInLine: {bytePositionInLine}.";
+                ex.SetMessage(message);
             }
         }
 
@@ -216,9 +214,17 @@ namespace System.Text.Json
             ex.Path = path;
 
             // If the message is empty, use a default message with the Path.
-            if (string.IsNullOrEmpty(ex.Message))
+            string message = ex.Message;
+            if (string.IsNullOrEmpty(message))
             {
-                ex.SetMessage(SR.Format(SR.SerializeUnableToSerialize, path));
+                message = SR.Format(SR.SerializeUnableToSerialize);
+                ex.AppendPathInformation = true;
+            }
+
+            if (ex.AppendPathInformation)
+            {
+                message += $" Path: {path}.";
+                ex.SetMessage(message);
             }
         }
 
@@ -240,8 +246,6 @@ namespace System.Text.Json
             throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateTypeAttribute, classType, attribute));
         }
 
-
-
         [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
         {
index f635521..f889b0d 100644 (file)
@@ -10,6 +10,8 @@ namespace System.Text.Json.Serialization.Tests
 {
     public static partial class ValueTests
     {
+        public static bool IsX64 { get; } = Environment.Is64BitProcess;
+
         [Fact]
         public static void ReadPrimitives()
         {
@@ -362,5 +364,32 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Equal(netcoreExpectedValue, testCode());
             }
         }
+
+        // NOTE: LongInputString test is constrained to run on Windows and MacOSX because it causes
+        //       problems on Linux due to the way deferred memory allocation works. On Linux, the allocation can
+        //       succeed even if there is not enough memory but then the test may get killed by the OOM killer at the
+        //       time the memory is accessed which triggers the full memory allocation.
+        private const int MaxExpansionFactorWhileTranscoding = 3;
+        private const int MaxArrayLengthBeforeCalculatingSize = int.MaxValue / 2 / MaxExpansionFactorWhileTranscoding;
+        [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)]
+        [ConditionalTheory(nameof(IsX64))]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize - 3)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize - 2)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize - 1)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize + 1)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize + 2)]
+        [InlineData(MaxArrayLengthBeforeCalculatingSize + 3)]
+        [OuterLoop]
+        public static void LongInputString(int length)
+        {
+            // Verify boundary conditions in Deserialize() that inspect the size to determine allocation strategy.
+            string repeated = new string('x', length - 2);
+            string json = $"\"{repeated}\"";
+            Assert.Equal(length, json.Length);
+
+            string str = JsonSerializer.Deserialize<string>(json);
+            Assert.Equal(repeated, str);
+        }
     }
 }