New converter model additional refactoring and feedback
authorSteve Harter <steveharter@users.noreply.github.com>
Tue, 28 Jan 2020 19:02:57 +0000 (13:02 -0600)
committerSteve Harter <steveharter@users.noreply.github.com>
Tue, 11 Feb 2020 19:27:26 +0000 (13:27 -0600)
75 files changed:
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonArrayConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentQueueOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentStackOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonICollectionOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIEnumerableConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIEnumerableConverterFactory.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIEnumerableDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIEnumerableOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIEnumerableWithAddMethodConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIListConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIListOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIReadOnlyDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonISetOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonImmutableDictionaryOfStringTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonImmutableEnumerableOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonListOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonObjectConverterFactory.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonObjectFactoryConverter.cs with 73% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonObjectDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonQueueOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonStackOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterKeyValuePair.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterNullableFactory.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ExtensionMethods.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonCollectionConverter.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIEnumerableConverter.cs with 51% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.ReadAhead.cs
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/JsonObjectConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.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.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.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.HandleMetadata.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonValueConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StackFrameObjectState.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StackFramePropertyState.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests.BadConverters.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/ExceptionTests.cs
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlingTests.Deserialize.cs
src/libraries/System.Text.Json/tests/Serialization/Stream.WriteTests.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ConcurrentCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.SpecializedCollections.cs

index f1d11f8..226f99b 100644 (file)
   <data name="SerializationDuplicateTypeAttribute" xml:space="preserve">
     <value>The type '{0}' cannot have more than one property that has the attribute '{1}'.</value>
   </data>
-  <data name="SerializationNotSupportedCollectionType" xml:space="preserve">
-    <value>The collection type '{0}' is not supported.</value>
+  <data name="SerializationNotSupportedType" xml:space="preserve">
+    <value>The type '{0}' is not supported.</value>
   </data>
-  <data name="SerializationNotSupportedCollection" xml:space="preserve">
-    <value>The collection type '{0}' on '{1}' is not supported.</value>
+  <data name="SerializationNotSupported" xml:space="preserve">
+    <value>The type '{0}' on '{1}' is not supported.</value>
   </data>
   <data name="InvalidCharacterAtStartOfComment" xml:space="preserve">
     <value>'{0}' is invalid after '/' at the beginning of the comment. Expected either '/' or '*'.</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>
-</root>
+</root>
\ No newline at end of file
index 887481f..7e7ba36 100644 (file)
@@ -77,7 +77,7 @@
     <Compile Include="System\Text\Json\Serialization\Converters\JsonKeyValuePairConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonListOfTConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonObjectDefaultConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\JsonObjectFactoryConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonObjectConverterFactory.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonQueueOfTConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonStackOfTConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterBoolean.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonCollectionConverter.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\JsonDefaultNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonDictionaryConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonExtensionDataAttribute.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonIEnumerableConverter.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\Node\JsonObjectProperty.cs" />
     <Compile Include="System\Text\Json\Node\JsonString.cs" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index e9fbe8f..ce687af 100644 (file)
@@ -2,13 +2,24 @@
 // 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.Runtime.CompilerServices;
 
 namespace System.Text.Json
 {
     internal static partial class JsonHelpers
     {
+        /// <summary>
+        /// Returns the span for the given reader.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<byte> GetSpan(this ref Utf8JsonReader reader)
+        {
+            return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+        }
+
 #if !BUILDING_INBOX_LIBRARY
         /// <summary>
         /// Returns <see langword="true"/> if <paramref name="value"/> is a valid Unicode scalar
@@ -65,6 +76,17 @@ namespace System.Text.Json
         public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';
 
         /// <summary>
+        /// Perform a Read() with a Debug.Assert verifying the reader did not return false.
+        /// This should be called when the Read() return value is not used, such as non-Stream cases where there is only one buffer.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ReadWithVerify(this ref Utf8JsonReader reader)
+        {
+            bool result = reader.Read();
+            Debug.Assert(result);
+        }
+
+        /// <summary>
         /// Calls Encoding.UTF8.GetString that supports netstandard.
         /// </summary>
         /// <param name="bytes">The utf8 bytes to convert.</param>
index 56608bb..4d3e2fc 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -14,10 +15,11 @@ namespace System.Text.Json.Serialization.Converters
         : JsonIEnumerableDefaultConverter<TCollection, TElement>
         where TCollection: IEnumerable
     {
-        internal override bool CanHaveMetadata => false;
+        internal override bool CanHaveIdMetadata => false;
 
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is List<TElement>);
             ((List<TElement>)state.Current.ReturnValue!).Add(value);
         }
 
@@ -28,12 +30,14 @@ namespace System.Text.Json.Serialization.Converters
 
         protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options)
         {
+            Debug.Assert(state.Current.ReturnValue is List<TElement>);
             List<TElement> list = (List<TElement>)state.Current.ReturnValue!;
             state.Current.ReturnValue = list.ToArray();
         }
 
         protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
         {
+            Debug.Assert(value is TElement[]);
             TElement[] array = (TElement[])(IEnumerable)value;
 
             int index = state.Current.EnumeratorIndex;
@@ -63,8 +67,6 @@ namespace System.Text.Json.Serialization.Converters
                         state.Current.EnumeratorIndex = ++index;
                         return false;
                     }
-
-                    state.Current.EndElement();
                 }
             }
 
index 4e695e8..4a4a10e 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -12,6 +13,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Enqueue(value);
         }
 
@@ -19,7 +21,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -38,6 +40,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -56,13 +59,9 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
-
-        internal override Type RuntimeType => typeof(Queue<TElement>);
     }
 }
index fe184a7..5321d8d 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -12,6 +13,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Push(value);
         }
 
@@ -19,7 +21,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -38,6 +40,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -56,13 +59,9 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
-
-        internal override Type RuntimeType => TypeToConvert;
     }
 }
index 468d33d..62f64bf 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.Diagnostics;
+
 namespace System.Text.Json.Serialization.Converters
 {
     /// <summary>
@@ -16,7 +18,7 @@ namespace System.Text.Json.Serialization.Converters
         protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
 
         /// <summary>
-        /// When overridden, converts the temporary collection held in state.ReturnValue to the final collection.
+        /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
         /// This is used with immutable collections.
         /// </summary>
         protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
@@ -30,13 +32,10 @@ namespace System.Text.Json.Serialization.Converters
 
         protected static JsonConverter<TValue> GetElementConverter(ref ReadStack state)
         {
-            JsonConverter<TValue>? converter = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase as JsonConverter<TValue>;
-            if (converter == null)
-            {
-                state.Current.JsonClassInfo.ElementClassInfo.PolicyProperty.ThrowCollectionNotSupportedException();
-            }
+            JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+            Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
 
-            return converter!;
+            return converter;
         }
 
         protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOptions options)
@@ -54,18 +53,15 @@ namespace System.Text.Json.Serialization.Converters
             return key;
         }
 
-        protected JsonConverter<TValue> GetValueConverter(ref WriteStack state)
+        protected static JsonConverter<TValue> GetValueConverter(ref WriteStack state)
         {
-            JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.DeclaredJsonPropertyInfo.ConverterBase;
-            if (converter == null)
-            {
-                state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ThrowCollectionNotSupportedException();
-            }
+            JsonConverter<TValue> converter = (JsonConverter<TValue>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+            Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
 
-            return converter!;
+            return converter;
         }
 
-        internal override sealed bool OnTryRead(
+        internal sealed override bool OnTryRead(
             ref Utf8JsonReader reader,
             Type typeToConvert,
             JsonSerializerOptions options,
@@ -88,10 +84,11 @@ namespace System.Text.Json.Serialization.Converters
                 JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
                 if (elementConverter.CanUseDirectReadOrWrite)
                 {
+                    // Process all elements.
                     while (true)
                     {
                         // Read the key name.
-                        reader.Read();
+                        reader.ReadWithVerify();
 
                         if (reader.TokenType == JsonTokenType.EndObject)
                         {
@@ -103,20 +100,21 @@ namespace System.Text.Json.Serialization.Converters
                             ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                         }
 
-                        state.Current.KeyName = reader.GetString();
+                        state.Current.JsonPropertyNameAsString = reader.GetString();
 
                         // Read the value and add.
-                        reader.Read();
+                        reader.ReadWithVerify();
                         TValue element = elementConverter.Read(ref reader, typeof(TValue), options);
                         Add(element, options, ref state);
                     }
                 }
                 else
                 {
+                    // Process all elements.
                     while (true)
                     {
                         // Read the key name.
-                        reader.Read();
+                        reader.ReadWithVerify();
 
                         if (reader.TokenType == JsonTokenType.EndObject)
                         {
@@ -128,10 +126,11 @@ namespace System.Text.Json.Serialization.Converters
                             ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                         }
 
-                        state.Current.KeyName = reader.GetString();
+                        state.Current.JsonPropertyNameAsString = reader.GetString();
 
-                        // Read the value and add.
-                        reader.Read();
+                        reader.ReadWithVerify();
+
+                        // Get the value from the converter and add it.
                         elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
                         Add(element, options, ref state);
                     }
@@ -139,7 +138,9 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
-                if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+                // Slower path that supports continuation and preserved references.
+
+                if (state.Current.ObjectState == StackFrameObjectState.None)
                 {
                     if (reader.TokenType != JsonTokenType.StartObject)
                     {
@@ -150,49 +151,47 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Handle the metadata properties.
-                if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+                if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
                 {
-                    if (this.ResolveMetadata(ref reader, ref state, out value))
+                    if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
                     {
                         if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
                         {
+                            value = (TCollection)state.Current.ReturnValue!;
                             return true;
                         }
                     }
                     else
                     {
+                        value = default!;
                         return false;
                     }
                 }
 
+                // Create the dictionary.
                 if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
                 {
                     CreateCollection(ref state);
 
                     if (state.Current.MetadataId != null)
                     {
-                        if (!CanHaveMetadata)
-                        {
-                            ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(TypeToConvert);
-                        }
+                        Debug.Assert(CanHaveIdMetadata);
 
                         value = (TCollection)state.Current.ReturnValue!;
                         if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
                         {
-                            // Reset so JsonPath throws exception with $id in it.
-                            state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
-                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
                         }
                     }
 
                     state.Current.ObjectState = StackFrameObjectState.CreatedObject;
                 }
 
+                // Process all elements.
                 JsonConverter<TValue> elementConverter = GetElementConverter(ref state);
                 while (true)
                 {
-                    if (state.Current.PropertyState < StackFramePropertyState.ReadName)
+                    if (state.Current.PropertyState == StackFramePropertyState.None)
                     {
                         state.Current.PropertyState = StackFramePropertyState.ReadName;
 
@@ -222,14 +221,14 @@ namespace System.Text.Json.Serialization.Converters
                         // Verify property doesn't contain metadata.
                         if (shouldReadPreservedReferences)
                         {
-                            ReadOnlySpan<byte> propertyName = JsonSerializer.GetSpan(ref reader);
+                            ReadOnlySpan<byte> propertyName = reader.GetSpan();
                             if (propertyName.Length > 0 && propertyName[0] == '$')
                             {
-                                ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+                                ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
                             }
                         }
 
-                        state.Current.KeyName = reader.GetString();
+                        state.Current.JsonPropertyNameAsString = reader.GetString();
                     }
 
                     if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
@@ -245,7 +244,7 @@ namespace System.Text.Json.Serialization.Converters
 
                     if (state.Current.PropertyState < StackFramePropertyState.TryRead)
                     {
-                        // Read the value and add.
+                        // Get the value from the converter and add it.
                         bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element);
                         if (!success)
                         {
@@ -264,46 +263,41 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        internal override sealed bool OnTryWrite(
+        internal sealed override bool OnTryWrite(
             Utf8JsonWriter writer,
             TCollection dictionary,
             JsonSerializerOptions options,
             ref WriteStack state)
         {
-            bool success;
-
             if (dictionary == null)
             {
                 writer.WriteNullValue();
-                success = true;
+                return true;
             }
-            else
+
+            if (!state.Current.ProcessedStartToken)
             {
-                if (!state.Current.ProcessedStartToken)
-                {
-                    state.Current.ProcessedStartToken = true;
-                    writer.WriteStartObject();
+                state.Current.ProcessedStartToken = true;
+                writer.WriteStartObject();
 
-                    if (options.ReferenceHandling.ShouldWritePreservedReferences())
+                if (options.ReferenceHandling.ShouldWritePreservedReferences())
+                {
+                    if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
                     {
-                        if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
-                        {
-                            writer.WriteEndObject();
-                            return true;
-                        }
+                        return true;
                     }
-
-                    state.Current.DeclaredJsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!;
                 }
 
-                success = OnWriteResume(writer, dictionary, options, ref state);
-                if (success)
+                state.Current.DeclaredJsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!;
+            }
+
+            bool success = OnWriteResume(writer, dictionary, options, ref state);
+            if (success)
+            {
+                if (!state.Current.ProcessedEndToken)
                 {
-                    if (!state.Current.ProcessedEndToken)
-                    {
-                        state.Current.ProcessedEndToken = true;
-                        writer.WriteEndObject();
-                    }
+                    state.Current.ProcessedEndToken = true;
+                    writer.WriteEndObject();
                 }
             }
 
index ab2343c..ef539d2 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -16,7 +17,9 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
         {
-            string key = state.Current.KeyName!;
+            Debug.Assert(state.Current.ReturnValue is TCollection);
+
+            string key = state.Current.JsonPropertyNameAsString!;
             ((TCollection)state.Current.ReturnValue!)[key] = value;
         }
 
@@ -24,7 +27,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -47,6 +50,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
                 enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
             }
 
@@ -65,6 +69,12 @@ namespace System.Text.Json.Serialization.Converters
             {
                 do
                 {
+                    if (ShouldFlush(writer, ref state))
+                    {
+                        state.Current.CollectionEnumerator = enumerator;
+                        return false;
+                    }
+
                     TValue element = enumerator.Current.Value;
                     if (state.Current.PropertyState < StackFramePropertyState.Name)
                     {
@@ -79,7 +89,7 @@ namespace System.Text.Json.Serialization.Converters
                         return false;
                     }
 
-                    state.Current.EndElement();
+                    state.Current.EndDictionaryElement();
                 } while (enumerator.MoveNext());
             }
 
index 739dec4..7a63ae1 100644 (file)
@@ -16,6 +16,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is ICollection<TElement>);
             ((ICollection<TElement>)state.Current.ReturnValue!).Add(value);
         }
 
@@ -23,7 +24,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
@@ -38,17 +39,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -69,6 +68,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -87,8 +87,6 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
index df905f0..814e1b9 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -12,12 +13,14 @@ namespace System.Text.Json.Serialization.Converters
     /// representing the dictionary element key and value.
     /// </summary>
     internal sealed class JsonIDictionaryConverter<TCollection>
-        : JsonDictionaryDefaultConverter<TCollection, object>
+        : JsonDictionaryDefaultConverter<TCollection, object?>
         where TCollection : IDictionary
     {
-        protected override void Add(object value, JsonSerializerOptions options, ref ReadStack state)
+        protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
         {
-            string key = state.Current.KeyName!;
+            Debug.Assert(state.Current.ReturnValue is IDictionary);
+
+            string key = state.Current.JsonPropertyNameAsString!;
             ((IDictionary)state.Current.ReturnValue!)[key] = value;
         }
 
@@ -25,7 +28,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
@@ -40,17 +43,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -67,31 +68,31 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IDictionaryEnumerator);
                 enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<object> converter = GetValueConverter(ref state);
+            JsonConverter<object?> converter = GetValueConverter(ref state);
             do
             {
-                if (!(enumerator.Key is string key))
+                if (enumerator.Key is string key)
                 {
-                    // todo: add test for this.
-                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo.RuntimePropertyType!);
-                    Diagnostics.Debug.Assert(false);
-                    return false;
-                }
+                    key = GetKeyName(key, ref state, options);
+                    writer.WritePropertyName(key);
 
-                key = GetKeyName(key, ref state, options);
-                writer.WritePropertyName(key);
+                    object? element = enumerator.Value;
+                    if (!converter.TryWrite(writer, element, options, ref state))
+                    {
+                        state.Current.CollectionEnumerator = enumerator;
+                        return false;
+                    }
 
-                object? element = enumerator.Value;
-                if (!converter.TryWrite(writer, element!, options, ref state))
+                    state.Current.EndDictionaryElement();
+                }
+                else
                 {
-                    state.Current.CollectionEnumerator = enumerator;
-                    return false;
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo!.RuntimePropertyType!);
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
index a3858ad..41e03f1 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -16,7 +17,9 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
         {
-            string key = state.Current.KeyName!;
+            Debug.Assert(state.Current.ReturnValue is TCollection);
+
+            string key = state.Current.JsonPropertyNameAsString!;
             ((TCollection)state.Current.ReturnValue!)[key] = value;
         }
 
@@ -24,7 +27,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
@@ -39,17 +42,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -70,12 +71,19 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
-                enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<KeyValuePair<string, TValue>>);
+                enumerator = (IEnumerator<KeyValuePair<string, TValue>>)state.Current.CollectionEnumerator;
             }
 
             JsonConverter<TValue> converter = GetValueConverter(ref state);
             do
             {
+                if (ShouldFlush(writer, ref state))
+                {
+                    state.Current.CollectionEnumerator = enumerator;
+                    return false;
+                }
+
                 string key = GetKeyName(enumerator.Current.Key, ref state, options);
                 writer.WritePropertyName(key);
 
@@ -86,7 +94,7 @@ namespace System.Text.Json.Serialization.Converters
                     return false;
                 }
 
-                state.Current.EndElement();
+                state.Current.EndDictionaryElement();
             } while (enumerator.MoveNext());
 
             return true;
index 049b3a8..2712385 100644 (file)
@@ -13,12 +13,13 @@ namespace System.Text.Json.Serialization.Converters
     /// </summary>
     /// <typeparam name="TCollection"></typeparam>
     internal sealed class JsonIEnumerableConverter<TCollection>
-        : JsonIEnumerableDefaultConverter<TCollection, object>
+        : JsonIEnumerableDefaultConverter<TCollection, object?>
         where TCollection : IEnumerable
     {
-        protected override void Add(object value, ref ReadStack state)
+        protected override void Add(object? value, ref ReadStack state)
         {
-            ((List<object>)state.Current.ReturnValue!).Add(value);
+            Debug.Assert(state.Current.ReturnValue is List<object?>);
+            ((List<object?>)state.Current.ReturnValue!).Add(value);
         }
 
         protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
@@ -28,7 +29,7 @@ namespace System.Text.Json.Serialization.Converters
                 ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
             }
 
-            state.Current.ReturnValue = new List<object>();
+            state.Current.ReturnValue = new List<object?>();
         }
 
         // Consider overriding ConvertCollection to convert the list to an array since a List is mutable.
@@ -54,7 +55,7 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<object> converter = GetElementConverter(ref state);
+            JsonConverter<object?> converter = GetElementConverter(ref state);
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -63,18 +64,16 @@ namespace System.Text.Json.Serialization.Converters
                     return false;
                 }
 
-                if (!converter.TryWrite(writer, enumerator.Current!, options, ref state))
+                if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
 
-        internal override Type RuntimeType => typeof(List<object>);
+        internal override Type RuntimeType => typeof(List<object?>);
     }
 }
index ac4643d..0081359 100644 (file)
@@ -16,9 +16,9 @@ namespace System.Text.Json.Serialization.Converters
     /// </summary>
     internal class JsonIEnumerableConverterFactory : JsonConverterFactory
     {
-        private static readonly JsonIDictionaryConverter<IDictionary> s_IDictionaryConverter = new JsonIDictionaryConverter<IDictionary>();
-        private static readonly JsonIEnumerableConverter<IEnumerable> s_IEnumerableConverter = new JsonIEnumerableConverter<IEnumerable>();
-        private static readonly JsonIListConverter<IList> s_IListConverter = new JsonIListConverter<IList>();
+        private static readonly JsonIDictionaryConverter<IDictionary> s_converterForIDictionary = new JsonIDictionaryConverter<IDictionary>();
+        private static readonly JsonIEnumerableConverter<IEnumerable> s_converterForIEnumerable = new JsonIEnumerableConverter<IEnumerable>();
+        private static readonly JsonIListConverter<IList> s_converterForIList = new JsonIListConverter<IList>();
 
         public override bool CanConvert(Type typeToConvert)
         {
@@ -47,6 +47,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonConverter? converter = null;
             Type converterType;
+            Type[] genericArgs;
             Type? elementType = null;
             Type? actualTypeToConvert;
 
@@ -71,10 +72,11 @@ namespace System.Text.Json.Serialization.Converters
             // Dictionary<string,> or deriving from Dictionary<string,>
             else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericBaseClass(typeof(Dictionary<,>))) != null)
             {
-                if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+                genericArgs = actualTypeToConvert.GetGenericArguments();
+                if (genericArgs[0] == typeof(string))
                 {
                     converterType = typeof(JsonDictionaryOfStringTValueConverter<,>);
-                    elementType = actualTypeToConvert.GetGenericArguments()[1];
+                    elementType = genericArgs[1];
                 }
                 else
                 {
@@ -84,10 +86,11 @@ namespace System.Text.Json.Serialization.Converters
             // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary<string, TValue>
             else if (typeToConvert.IsImmutableDictionaryType())
             {
-                if (typeToConvert.GetGenericArguments()[0] == typeof(string))
+                genericArgs = typeToConvert.GetGenericArguments();
+                if (genericArgs[0] == typeof(string))
                 {
                     converterType = typeof(JsonImmutableDictionaryOfStringTValueConverter<,>);
-                    elementType = typeToConvert.GetGenericArguments()[1];
+                    elementType = genericArgs[1];
                 }
                 else
                 {
@@ -97,10 +100,11 @@ namespace System.Text.Json.Serialization.Converters
             // IDictionary<string,> or deriving from IDictionary<string,>
             else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IDictionary<,>))) != null)
             {
-                if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+                genericArgs = actualTypeToConvert.GetGenericArguments();
+                if (genericArgs[0] == typeof(string))
                 {
                     converterType = typeof(JsonIDictionaryOfStringTValueConverter<,>);
-                    elementType = actualTypeToConvert.GetGenericArguments()[1];
+                    elementType = genericArgs[1];
                 }
                 else
                 {
@@ -110,10 +114,11 @@ namespace System.Text.Json.Serialization.Converters
             // IReadOnlyDictionary<string,> or deriving from IReadOnlyDictionary<string,>
             else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>))) != null)
             {
-                if (actualTypeToConvert.GetGenericArguments()[0] == typeof(string))
+                genericArgs = actualTypeToConvert.GetGenericArguments();
+                if (genericArgs[0] == typeof(string))
                 {
                     converterType = typeof(JsonIReadOnlyDictionaryOfStringTValueConverter<,>);
-                    elementType = actualTypeToConvert.GetGenericArguments()[1];
+                    elementType = genericArgs[1];
                 }
                 else
                 {
@@ -179,7 +184,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (typeToConvert == typeof(IDictionary))
                 {
-                    return s_IDictionaryConverter;
+                    return s_converterForIDictionary;
                 }
 
                 converterType = typeof(JsonIDictionaryConverter<>);
@@ -188,7 +193,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (typeToConvert == typeof(IList))
                 {
-                    return s_IListConverter;
+                    return s_converterForIList;
                 }
 
                 converterType = typeof(JsonIListConverter<>);
@@ -202,7 +207,7 @@ namespace System.Text.Json.Serialization.Converters
                 Debug.Assert(typeof(IEnumerable).IsAssignableFrom(typeToConvert));
                 if (typeToConvert == typeof(IEnumerable))
                 {
-                    return s_IEnumerableConverter;
+                    return s_converterForIEnumerable;
                 }
 
                 converterType = typeof(JsonIEnumerableConverter<>);
@@ -222,7 +227,7 @@ namespace System.Text.Json.Serialization.Converters
 
                 converter = (JsonConverter)Activator.CreateInstance(
                     genericType,
-                    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
+                    BindingFlags.Instance | BindingFlags.Public,
                     binder: null,
                     args: null,
                     culture: null)!;
index 7062b35..b4911e4 100644 (file)
@@ -2,38 +2,33 @@
 // 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>
     /// Default base class implementation of <cref>JsonIEnumerableConverter{TCollection, TElement}</cref>.
     /// </summary>
-    internal abstract class JsonIEnumerableDefaultConverter<TCollection, TElement> : JsonIEnumerableConverter<TCollection, TElement>
+    internal abstract class JsonIEnumerableDefaultConverter<TCollection, TElement> : JsonCollectionConverter<TCollection, TElement>
     {
         protected abstract void Add(TElement value, ref ReadStack state);
-
-        protected virtual void CreateCollection(ref ReadStack state, JsonSerializerOptions options) { }
+        protected abstract void CreateCollection(ref ReadStack state, JsonSerializerOptions options);
         protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
 
         protected static JsonConverter<TElement> GetElementConverter(ref ReadStack state)
         {
-            JsonConverter<TElement>? converter = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase as JsonConverter<TElement>;
-            if (converter == null)
-            {
-                state.Current.JsonClassInfo.ElementClassInfo.PolicyProperty.ThrowCollectionNotSupportedException();
-            }
+            JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+            Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
 
-            return converter!;
+            return converter;
         }
 
         protected static JsonConverter<TElement> GetElementConverter(ref WriteStack state)
         {
-            JsonConverter<TElement>? converter = state.Current.DeclaredJsonPropertyInfo.ConverterBase as JsonConverter<TElement>;
-            if (converter == null)
-            {
-                state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!.ThrowCollectionNotSupportedException();
-            }
+            JsonConverter<TElement> converter = (JsonConverter<TElement>)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+            Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
 
-            return converter!;
+            return converter;
         }
 
         internal override bool OnTryRead(
@@ -62,7 +57,7 @@ namespace System.Text.Json.Serialization.Converters
                     // Fast path that avoids validation and extra indirection.
                     while (true)
                     {
-                        reader.Read();
+                        reader.ReadWithVerify();
                         if (reader.TokenType == JsonTokenType.EndArray)
                         {
                             break;
@@ -75,15 +70,16 @@ namespace System.Text.Json.Serialization.Converters
                 }
                 else
                 {
+                    // Process all elements.
                     while (true)
                     {
-                        reader.Read();
+                        reader.ReadWithVerify();
                         if (reader.TokenType == JsonTokenType.EndArray)
                         {
                             break;
                         }
 
-                        // Obtain the CLR value from the JSON and apply to the object.
+                        // Get the value from the converter and add it.
                         elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement element);
                         Add(element, ref state);
                     }
@@ -91,11 +87,13 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
-                if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+                // Slower path that supports continuation and preserved references.
+
+                if (state.Current.ObjectState == StackFrameObjectState.None)
                 {
                     if (reader.TokenType == JsonTokenType.StartArray)
                     {
-                        state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+                        state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
                     }
                     else if (shouldReadPreservedReferences)
                     {
@@ -113,17 +111,19 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Handle the metadata properties.
-                if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+                if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
                 {
-                    if (this.ResolveMetadata(ref reader, ref state, out value))
+                    if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
                     {
                         if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
                         {
+                            value = (TCollection)state.Current.ReturnValue!;
                             return true;
                         }
                     }
                     else
                     {
+                        value = default!;
                         return false;
                     }
                 }
@@ -137,10 +137,7 @@ namespace System.Text.Json.Serialization.Converters
                         value = (TCollection)state.Current.ReturnValue!;
                         if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
                         {
-                            // Reset so JsonPath throws exception with $id in it.
-                            state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
-                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
                         }
                     }
 
@@ -152,7 +149,7 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     JsonConverter<TElement> elementConverter = GetElementConverter(ref state);
 
-                    // Main loop for processing elements.
+                    // Process all elements.
                     while (true)
                     {
                         if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
@@ -170,10 +167,6 @@ namespace System.Text.Json.Serialization.Converters
                         {
                             if (reader.TokenType == JsonTokenType.EndArray)
                             {
-                                // Clear the MetadataPropertyName in case we were processing $values since
-                                // we are not longer processing $values.
-                                state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
-
                                 break;
                             }
 
@@ -182,7 +175,7 @@ namespace System.Text.Json.Serialization.Converters
 
                         if (state.Current.PropertyState < StackFramePropertyState.TryRead)
                         {
-                            // Obtain the CLR value from the JSON and apply to the object.
+                            // Get the value from the converter and add it.
                             if (!elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement element))
                             {
                                 value = default!;
@@ -190,6 +183,7 @@ namespace System.Text.Json.Serialization.Converters
                             }
 
                             Add(element, ref state);
+
                             // No need to set PropertyState to TryRead since we're done with this element now.
                             state.Current.EndElement();
                         }
@@ -213,7 +207,12 @@ namespace System.Text.Json.Serialization.Converters
 
                         if (reader.TokenType != JsonTokenType.EndObject)
                         {
-                            ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader, ref state);
+                            if (reader.TokenType == JsonTokenType.PropertyName)
+                            {
+                                state.Current.JsonPropertyName = reader.GetSpan().ToArray();
+                            }
+
+                            ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader);
                         }
                     }
                 }
@@ -224,7 +223,7 @@ namespace System.Text.Json.Serialization.Converters
                     {
                         if (reader.TokenType != JsonTokenType.EndObject)
                         {
-                            ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader, ref state);
+                            ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(typeToConvert, reader);
                         }
                     }
                 }
@@ -235,7 +234,7 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        internal override sealed bool OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
+        internal sealed override bool OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
         {
             bool success;
 
index c62792c..d1f2269 100644 (file)
@@ -16,6 +16,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is List<TElement>);
             ((List<TElement>)state.Current.ReturnValue!).Add(value);
         }
 
@@ -42,6 +43,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -60,8 +62,6 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
index 26f2dca..55bd1b2 100644 (file)
@@ -3,32 +3,32 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
-using System.Collections.Concurrent;
 using System.Diagnostics;
-using System.Reflection;
 
 namespace System.Text.Json.Serialization.Converters
 {
-    internal sealed class JsonIEnumerableWithAddMethodConverter<TCollection> : JsonIEnumerableDefaultConverter<TCollection, object>
+    internal sealed class JsonIEnumerableWithAddMethodConverter<TCollection> :
+        JsonIEnumerableDefaultConverter<TCollection, object?>
         where TCollection : IEnumerable
     {
-        protected override void Add(object value, ref ReadStack state)
+        protected override void Add(object? value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             Debug.Assert(state.Current.AddMethodDelegate != null);
-            ((Action<TCollection, object>)state.Current.AddMethodDelegate)((TCollection)state.Current.ReturnValue!, value);
+            ((Action<TCollection, object?>)state.Current.AddMethodDelegate)((TCollection)state.Current.ReturnValue!, value);
         }
 
         protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
         {
-            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            JsonClassInfo.ConstructorDelegate? constructorDelegate = state.Current.JsonClassInfo.CreateObject;
 
-            if (classInfo.CreateObject == null)
+            if (constructorDelegate == null)
             {
-                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(classInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
             }
 
-            state.Current.ReturnValue = classInfo.CreateObject()!;
-            state.Current.AddMethodDelegate = GetOrAddEnumerableAddMethodDelegate(classInfo.Type, options);
+            state.Current.ReturnValue = constructorDelegate();
+            state.Current.AddMethodDelegate = GetAddMethodDelegate(options);
         }
 
         protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
@@ -47,7 +47,7 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<object> converter = GetElementConverter(ref state);
+            JsonConverter<object?> converter = GetElementConverter(ref state);
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -56,31 +56,27 @@ namespace System.Text.Json.Serialization.Converters
                     return false;
                 }
 
-                if (!converter.TryWrite(writer, enumerator.Current!, options, ref state))
+                if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
 
-        internal override Type RuntimeType => TypeToConvert;
-
-        private readonly ConcurrentDictionary<Type, Action<TCollection, object>> _delegates = new ConcurrentDictionary<Type, Action<TCollection, object>>();
+        private Action<TCollection, object?>? _addMethodDelegate;
 
-        internal Action<TCollection, object> GetOrAddEnumerableAddMethodDelegate(Type type, JsonSerializerOptions options)
+        internal Action<TCollection, object?> GetAddMethodDelegate(JsonSerializerOptions options)
         {
-            if (!_delegates.TryGetValue(type, out Action<TCollection, object>? result))
+            if (_addMethodDelegate == null)
             {
                 // We verified this exists when we created the converter in the enumerable converter factory.
-                result = options.MemberAccessorStrategy.CreateAddMethodDelegate<TCollection>();
+                _addMethodDelegate = options.MemberAccessorStrategy.CreateAddMethodDelegate<TCollection>();
             }
 
-            return result;
+            return _addMethodDelegate;
         }
     }
 }
index 09aca54..71785fd 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -11,23 +12,24 @@ namespace System.Text.Json.Serialization.Converters
     internal sealed class JsonIListConverter<TCollection> : JsonIEnumerableDefaultConverter<TCollection, object>
         where TCollection : IList
     {
-        protected override void Add(object value, ref ReadStack state)
+        protected override void Add(object? value, ref ReadStack state)
         {
-            ((IList)state.Current.ReturnValue!).Add(value);
+            Debug.Assert(state.Current.ReturnValue is IList);
+            ((IList)state.Current.ReturnValue).Add(value);
         }
 
         protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
 
-                state.Current.ReturnValue = new List<object>();
+                state.Current.ReturnValue = new List<object?>();
             }
             else
             {
@@ -35,17 +37,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -65,7 +65,7 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<object> converter = JsonSerializerOptions.GetObjectConverter();
+            JsonConverter<object> converter = GetElementConverter(ref state);
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -81,8 +81,6 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
@@ -94,7 +92,7 @@ namespace System.Text.Json.Serialization.Converters
             {
                 if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
                 {
-                    return typeof(List<object>);
+                    return typeof(List<object?>);
                 }
 
                 return TypeToConvert;
index 4558d08..f227857 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -14,6 +15,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Add(value);
         }
 
@@ -21,7 +23,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
@@ -36,17 +38,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -63,6 +63,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -81,8 +82,6 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
index 2c5a2c0..5dfcc36 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -11,7 +12,9 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
         {
-            string key = state.Current.KeyName!;
+            Debug.Assert(state.Current.ReturnValue is Dictionary<string, TValue>);
+
+            string key = state.Current.JsonPropertyNameAsString!;
             ((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
         }
 
@@ -38,12 +41,19 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
                 enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
             }
 
             JsonConverter<TValue> converter = GetValueConverter(ref state);
             do
             {
+                if (ShouldFlush(writer, ref state))
+                {
+                    state.Current.CollectionEnumerator = enumerator;
+                    return false;
+                }
+
                 string key = GetKeyName(enumerator.Current.Key, ref state, options);
                 writer.WritePropertyName(key);
 
@@ -54,7 +64,7 @@ namespace System.Text.Json.Serialization.Converters
                     return false;
                 }
 
-                state.Current.EndElement();
+                state.Current.EndDictionaryElement();
             } while (enumerator.MoveNext());
 
             return true;
index 57954c9..eb9b79e 100644 (file)
@@ -12,6 +12,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Add(value);
         }
 
@@ -19,7 +20,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
 
-            if ((TypeToConvert.IsInterface || TypeToConvert.IsAbstract))
+            if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
             {
                 if (!TypeToConvert.IsAssignableFrom(RuntimeType))
                 {
@@ -34,17 +35,15 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
                 }
-                else
-                {
-                    TCollection returnValue = (TCollection)classInfo.CreateObject!()!;
 
-                    if (returnValue.IsReadOnly)
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(TypeToConvert);
-                    }
+                TCollection returnValue = (TCollection)classInfo.CreateObject()!;
 
-                    state.Current.ReturnValue = returnValue;
+                if (returnValue.IsReadOnly)
+                {
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
                 }
+
+                state.Current.ReturnValue = returnValue;
             }
         }
 
@@ -61,6 +60,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -79,8 +79,6 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
index ca19e5b..0e12a8e 100644 (file)
@@ -3,7 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
-using System.Collections.Concurrent;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -12,11 +12,13 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
         {
-            string key = state.Current.KeyName!;
+            Debug.Assert(state.Current.ReturnValue is Dictionary<string, TValue>);
+
+            string key = state.Current.JsonPropertyNameAsString!;
             ((Dictionary<string, TValue>)state.Current.ReturnValue!)[key] = value;
         }
 
-        internal override bool CanHaveMetadata => false;
+        internal override bool CanHaveIdMetadata => false;
 
         protected override void CreateCollection(ref ReadStack state)
         {
@@ -41,12 +43,19 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is Dictionary<string, TValue>.Enumerator);
                 enumerator = (Dictionary<string, TValue>.Enumerator)state.Current.CollectionEnumerator;
             }
 
             JsonConverter<TValue> converter = GetValueConverter(ref state);
             do
             {
+                if (ShouldFlush(writer, ref state))
+                {
+                    state.Current.CollectionEnumerator = enumerator;
+                    return false;
+                }
+
                 string key = GetKeyName(enumerator.Current.Key, ref state, options);
                 writer.WritePropertyName(key);
 
@@ -57,14 +66,12 @@ namespace System.Text.Json.Serialization.Converters
                     return false;
                 }
 
-                state.Current.EndElement();
+                state.Current.EndDictionaryElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
 
-        internal override Type RuntimeType => TypeToConvert;
-
         private Func<IEnumerable<KeyValuePair<string, TValue>>, TCollection>? _creatorDelegate;
 
         private Func<IEnumerable<KeyValuePair<string, TValue>>, TCollection> GetCreatorDelegate(JsonSerializerOptions options)
index d7e271a..d3cd9bc 100644 (file)
@@ -3,7 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
-using System.Collections.Concurrent;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -12,10 +12,11 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is List<TElement>);
             ((List<TElement>)state.Current.ReturnValue!).Add(value);
         }
 
-        internal override bool CanHaveMetadata => false;
+        internal override bool CanHaveIdMetadata => false;
 
         protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
         {
@@ -40,6 +41,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -58,15 +60,11 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
 
-        internal override Type RuntimeType => TypeToConvert;
-
         private Func<IEnumerable<TElement>, TCollection>? _creatorDelegate;
 
         private Func<IEnumerable<TElement>, TCollection> GetCreatorDelegate(JsonSerializerOptions options)
index ba7210e..31c9413 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -13,6 +14,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Add(value);
         }
 
@@ -20,7 +22,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -29,6 +31,8 @@ namespace System.Text.Json.Serialization.Converters
         protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
         {
             List<TElement> list = value;
+
+            // Using an index is 2x faster than using an enumerator.
             int index = state.Current.EnumeratorIndex;
             JsonConverter<TElement> elementConverter = GetElementConverter(ref state);
 
@@ -56,8 +60,6 @@ namespace System.Text.Json.Serialization.Converters
                         state.Current.EnumeratorIndex = ++index;
                         return false;
                     }
-
-                    state.Current.EndElement();
                 }
             }
 
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
+using System.Diagnostics;
 using System.Reflection;
 
 namespace System.Text.Json.Serialization.Converters
@@ -10,12 +11,14 @@ namespace System.Text.Json.Serialization.Converters
     /// <summary>
     /// Converter factory for all object-based types (non-enumerable and non-primitive).
     /// </summary>
-    internal class JsonObjectFactoryConverter : JsonConverterFactory
+    internal class JsonObjectConverterFactory : JsonConverterFactory
     {
         public override bool CanConvert(Type typeToConvert)
         {
-            // If the IEnumerableConverterFactory doesn't support the collection, ObjectConverterFactory doesn't either.
-            return !typeof(IEnumerable).IsAssignableFrom(typeToConvert);
+            // This is the last built-in factory converter, so if the IEnumerableConverterFactory doesn't
+            // support it, then it is not IEnumerable.
+            Debug.Assert(!typeof(IEnumerable).IsAssignableFrom(typeToConvert));
+            return true;
         }
 
         public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
index ba1b2df..49c701a 100644 (file)
@@ -32,11 +32,11 @@ namespace System.Text.Json.Serialization.Converters
 
                 obj = state.Current.JsonClassInfo.CreateObject!()!;
 
-                // Read all properties.
+                // Process all properties.
                 while (true)
                 {
                     // Read the property name or EndObject.
-                    reader.Read();
+                    reader.ReadWithVerify();
 
                     JsonTokenType tokenType = reader.TokenType;
                     if (tokenType == JsonTokenType.EndObject)
@@ -49,23 +49,23 @@ namespace System.Text.Json.Serialization.Converters
                         ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                     }
 
-                    JsonSerializer.LookupProperty(
+                    JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
                         obj,
                         ref reader,
                         options,
                         ref state,
-                        out JsonPropertyInfo jsonPropertyInfo,
                         out bool useExtensionProperty);
 
                     // Skip the property if not found.
                     if (!jsonPropertyInfo.ShouldDeserialize)
                     {
-                        reader.TrySkip();
+                        reader.Skip();
+                        state.Current.EndProperty();
                         continue;
                     }
 
                     // Set the property value.
-                    reader.Read();
+                    reader.ReadWithVerify();
 
                     if (!useExtensionProperty)
                     {
@@ -75,11 +75,16 @@ namespace System.Text.Json.Serialization.Converters
                     {
                         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();
                 }
             }
             else
             {
-                if (state.Current.ObjectState < StackFrameObjectState.StartToken)
+                // Slower path that supports continuation and preserved references.
+
+                if (state.Current.ObjectState == StackFrameObjectState.None)
                 {
                     if (reader.TokenType != JsonTokenType.StartObject)
                     {
@@ -90,29 +95,26 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Handle the metadata properties.
-                if (state.Current.ObjectState < StackFrameObjectState.MetataPropertyValue)
+                if (state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
                 {
                     if (shouldReadPreservedReferences)
                     {
-                        if (this.ResolveMetadata(ref reader, ref state, out value))
+                        if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
                         {
                             if (state.Current.ObjectState == StackFrameObjectState.MetadataRefPropertyEndObject)
                             {
-                                if (!CanHaveMetadata)
-                                {
-                                    ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(TypeToConvert);
-                                }
-
+                                value = (T)state.Current.ReturnValue!;
                                 return true;
                             }
                         }
                         else
                         {
+                            value = default!;
                             return false;
                         }
                     }
 
-                    state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+                    state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
                 }
 
                 if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
@@ -127,10 +129,7 @@ namespace System.Text.Json.Serialization.Converters
                     {
                         if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, obj))
                         {
-                            // Reset so JsonPath throws exception with $id in it.
-                            state.Current.MetadataPropertyName = MetadataPropertyName.Id;
-
-                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId);
+                            ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
                         }
                     }
 
@@ -143,11 +142,11 @@ namespace System.Text.Json.Serialization.Converters
                     Debug.Assert(obj != null);
                 }
 
-                // Read all properties.
+                // Process all properties.
                 while (true)
                 {
                     // Determine the property.
-                    if (state.Current.PropertyState < StackFramePropertyState.ReadName)
+                    if (state.Current.PropertyState == StackFramePropertyState.None)
                     {
                         state.Current.PropertyState = StackFramePropertyState.ReadName;
 
@@ -177,19 +176,19 @@ namespace System.Text.Json.Serialization.Converters
                             ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                         }
 
-                        JsonSerializer.LookupProperty(
+                        jsonPropertyInfo = JsonSerializer.LookupProperty(
                             obj,
                             ref reader,
                             options,
                             ref state,
-                            out jsonPropertyInfo,
                             out bool useExtensionProperty);
 
                         state.Current.UseExtensionProperty = useExtensionProperty;
                     }
                     else
                     {
-                        jsonPropertyInfo = state.Current.JsonPropertyInfo;
+                        Debug.Assert(state.Current.JsonPropertyInfo != null);
+                        jsonPropertyInfo = state.Current.JsonPropertyInfo!;
                     }
 
                     if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
@@ -289,23 +288,18 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
                     {
-                        writer.WriteEndObject();
                         return true;
                     }
                 }
 
                 JsonPropertyInfo? dataExtensionProperty = state.Current.JsonClassInfo.DataExtensionProperty;
 
-                int propertyCount;
+                int propertyCount = 0;
                 JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonClassInfo.PropertyCacheArray;
                 if (propertyCacheArray != null)
                 {
                     propertyCount = propertyCacheArray.Length;
                 }
-                else
-                {
-                    propertyCount = 0;
-                }
 
                 for (int i = 0; i < propertyCount; i++)
                 {
@@ -355,7 +349,6 @@ namespace System.Text.Json.Serialization.Converters
                     {
                         if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
                         {
-                            writer.WriteEndObject();
                             return true;
                         }
                     }
@@ -365,16 +358,12 @@ namespace System.Text.Json.Serialization.Converters
 
                 JsonPropertyInfo? dataExtensionProperty = state.Current.JsonClassInfo.DataExtensionProperty;
 
-                int propertyCount;
+                int propertyCount = 0;
                 JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonClassInfo.PropertyCacheArray;
                 if (propertyCacheArray != null)
                 {
                     propertyCount = propertyCacheArray.Length;
                 }
-                else
-                {
-                    propertyCount = 0;
-                }
 
                 while (propertyCount > state.Current.EnumeratorIndex)
                 {
index c40193e..a0cc38d 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -11,6 +12,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Enqueue(value);
         }
 
@@ -18,7 +20,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -37,6 +39,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -55,13 +58,9 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
-
-        internal override Type RuntimeType => typeof(Queue<TElement>);
     }
 }
index feb50c8..31dac18 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization.Converters
 {
@@ -11,6 +12,7 @@ namespace System.Text.Json.Serialization.Converters
     {
         protected override void Add(TElement value, ref ReadStack state)
         {
+            Debug.Assert(state.Current.ReturnValue is TCollection);
             ((TCollection)state.Current.ReturnValue!).Push(value);
         }
 
@@ -18,7 +20,7 @@ namespace System.Text.Json.Serialization.Converters
         {
             if (state.Current.JsonClassInfo.CreateObject == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(state.Current.JsonClassInfo.Type);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
             }
 
             state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
@@ -37,6 +39,7 @@ namespace System.Text.Json.Serialization.Converters
             }
             else
             {
+                Debug.Assert(state.Current.CollectionEnumerator is IEnumerator<TElement>);
                 enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
             }
 
@@ -55,13 +58,9 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
                 }
-
-                state.Current.EndElement();
             } while (enumerator.MoveNext());
 
             return true;
         }
-
-        internal override Type RuntimeType => TypeToConvert;
     }
 }
index 2c7f245..70eb76e 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Text.Json.Serialization.Converters
             bool valueSet = false;
 
             // Get the first property.
-            reader.Read();
+            reader.ReadWithVerify();
             if (reader.TokenType != JsonTokenType.PropertyName)
             {
                 ThrowHelper.ThrowJsonException();
@@ -45,13 +45,13 @@ namespace System.Text.Json.Serialization.Converters
             string propertyName = reader.GetString()!;
             if (propertyName == KeyName)
             {
-                reader.Read();
+                reader.ReadWithVerify();
                 k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName);
                 keySet = true;
             }
             else if (propertyName == ValueName)
             {
-                reader.Read();
+                reader.ReadWithVerify();
                 v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName);
                 valueSet = true;
             }
@@ -61,7 +61,7 @@ namespace System.Text.Json.Serialization.Converters
             }
 
             // Get the second property.
-            reader.Read();
+            reader.ReadWithVerify();
             if (reader.TokenType != JsonTokenType.PropertyName)
             {
                 ThrowHelper.ThrowJsonException();
@@ -70,13 +70,13 @@ namespace System.Text.Json.Serialization.Converters
             propertyName = reader.GetString()!;
             if (propertyName == KeyName)
             {
-                reader.Read();
+                reader.ReadWithVerify();
                 k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, KeyName);
                 keySet = true;
             }
             else if (propertyName == ValueName)
             {
-                reader.Read();
+                reader.ReadWithVerify();
                 v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, ValueName);
                 valueSet = true;
             }
@@ -90,7 +90,7 @@ namespace System.Text.Json.Serialization.Converters
                 ThrowHelper.ThrowJsonException();
             }
 
-            reader.Read();
+            reader.ReadWithVerify();
 
             if (reader.TokenType != JsonTokenType.EndObject)
             {
index b182e36..525793d 100644 (file)
@@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
     {
         // It is possible to cache the underlying converter since this is an internal converter and
         // an instance is created only once for each JsonSerializerOptions instance.
-        private JsonConverter<T> _converter;
+        private readonly JsonConverter<T> _converter;
 
         public JsonValueConverterNullable(JsonConverter<T> converter)
         {
index 78d039d..742b184 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
 using System.Reflection;
 
 namespace System.Text.Json.Serialization
@@ -15,13 +16,14 @@ namespace System.Text.Json.Serialization
 
         public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
         {
+            Debug.Assert(typeToConvert.GetGenericArguments().Length > 0);
+
             Type valueTypeToConvert = typeToConvert.GetGenericArguments()[0];
 
             JsonConverter? valueConverter = options.GetConverter(valueTypeToConvert);
             if (valueConverter == null)
             {
-                // todo: add test for this
-                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(valueTypeToConvert);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(valueTypeToConvert);
             }
 
             JsonConverter converter = (JsonConverter)Activator.CreateInstance(
index d8fa73b..703ba02 100644 (file)
@@ -2,12 +2,11 @@
 // 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.Buffers;
 using System.Diagnostics;
 using System.Reflection;
 
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
 {
     internal static class ExtensionMethods
     {
@@ -46,7 +45,7 @@ namespace System.Text.Json
             Debug.Assert(!baseType.IsInterface);
             Debug.Assert(baseType == baseType.GetGenericTypeDefinition());
 
-            Type baseTypeToCheck = type;
+            Type? baseTypeToCheck = type;
 
             while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
             {
@@ -59,7 +58,7 @@ namespace System.Text.Json
                     }
                 }
 
-                baseTypeToCheck = baseTypeToCheck.BaseType!;
+                baseTypeToCheck = baseTypeToCheck.BaseType;
             }
 
             return null;
@@ -98,27 +97,6 @@ namespace System.Text.Json
             return null;
         }
 
-        internal static Type? GetCompatibleNonGenericBaseClass(this Type type, string baseTypeAssemblyQualifiedNamePrefix)
-        {
-            Type baseTypeToCheck = type;
-
-            while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
-            {
-                if (!baseTypeToCheck.IsGenericType)
-                {
-                    Type nonGenericTypeToCheck = baseTypeToCheck;
-                    if (nonGenericTypeToCheck.AssemblyQualifiedName!.StartsWith(baseTypeAssemblyQualifiedNamePrefix))
-                    {
-                        return baseTypeToCheck;
-                    }
-                }
-
-                baseTypeToCheck = baseTypeToCheck.BaseType!;
-            }
-
-            return null;
-        }
-
         public static bool IsImmutableDictionaryType(this Type type)
         {
             if (!type.IsGenericType || !type.Assembly.FullName!.StartsWith("System.Collections.Immutable,"))
@@ -178,7 +156,7 @@ namespace System.Text.Json
                 }
             }
 
-            ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+            ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
             return null!;
         }
 
@@ -198,7 +176,7 @@ namespace System.Text.Json
                 }
             }
 
-            ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+            ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
             return null!;
         }
 
@@ -277,8 +255,13 @@ namespace System.Text.Json
 
         public static bool IsNonGenericStackOrQueue(this Type type)
         {
-            return type.GetCompatibleNonGenericBaseClass("System.Collections.Stack, System.Collections.NonGeneric") != null ||
-                type.GetCompatibleNonGenericBaseClass("System.Collections.Queue, System.Collections.NonGeneric") != null;
+            Type? typeOfStack = Type.GetType("System.Collections.Stack, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
+            Type? typeOfQueue = Type.GetType("System.Collections.Queue, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
+
+            Debug.Assert(typeOfStack != null);
+            Debug.Assert(typeOfQueue != null);
+
+            return typeOfStack.IsAssignableFrom(type) || typeOfQueue.IsAssignableFrom(type);
         }
     }
 }
index 287601e..87d2a2e 100644 (file)
@@ -29,7 +29,7 @@ namespace System.Text.Json
 
             if (converter == null)
             {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(propertyType, parentClassType, propertyInfo);
+                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(propertyType, parentClassType, propertyInfo);
             }
 
             return CreateProperty(
index 5c12c2a..a6f6961 100644 (file)
@@ -10,7 +10,6 @@ using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Converters;
 
 namespace System.Text.Json
 {
@@ -39,9 +38,6 @@ namespace System.Text.Json
         public delegate object? ConstructorDelegate();
         public ConstructorDelegate? CreateObject { get; private set; }
 
-        public delegate TCollection ConstructorDelegate<TCollection>(ICollection elements);
-        public delegate TCollection ConstructorDelegate<TCollection, TElement>(IEnumerable<TElement> elements);
-
         public ClassType ClassType { get; private set; }
 
         public JsonPropertyInfo? DataExtensionProperty { get; private set; }
@@ -118,7 +114,6 @@ namespace System.Text.Json
         {
             Type = type;
             Options = options;
-            JsonConverter? converter;
 
             ClassType = GetClassType(
                 type,
@@ -126,7 +121,7 @@ namespace System.Text.Json
                 propertyInfo: null,
                 out Type? runtimeType,
                 out Type? elementType,
-                out converter,
+                out JsonConverter? converter,
                 options);
 
             switch (ClassType)
@@ -215,7 +210,7 @@ namespace System.Text.Json
                     break;
                 case ClassType.Invalid:
                     {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
+                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
                     }
                     break;
                 default:
@@ -230,15 +225,13 @@ namespace System.Text.Json
             if (jsonPropertyInfo != null)
             {
                 Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType;
-                if (typeof(Dictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
-                    typeof(Dictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType) ||
-                    typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
+                if (typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType) ||
                     typeof(IDictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType))
                 {
                     JsonConverter? converter = Options.GetConverter(declaredPropertyType);
                     if (converter == null)
                     {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(declaredPropertyType);
+                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(declaredPropertyType);
                     }
                 }
                 else
@@ -247,7 +240,6 @@ namespace System.Text.Json
                 }
 
                 DataExtensionProperty = jsonPropertyInfo;
-                jsonPropertyInfo.EscapedName = null;
 
                 return true;
             }
@@ -571,17 +563,13 @@ namespace System.Text.Json
                     {
                         runtimeType = converterRuntimeType;
                     }
-                    else if (converterRuntimeType.IsAssignableFrom(type))
-                    {
-                        runtimeType = type;
-                    }
-                    else if (converter.TypeToConvert.IsAssignableFrom(type))
+                    else if (converterRuntimeType.IsAssignableFrom(type) || converter.TypeToConvert.IsAssignableFrom(type))
                     {
                         runtimeType = type;
                     }
                     else
                     {
-                        throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(type, parentClassType, propertyInfo);
+                        throw ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type, parentClassType, propertyInfo);
                     }
                 }
             }
@@ -5,14 +5,11 @@
 namespace System.Text.Json.Serialization
 {
     /// <summary>
-    /// Base class for IEnumerable-based collections.
+    /// Base class for all collections. Collections are assumed to implement <cref>System.Collections.IEnumerable</cref>.
     /// </summary>
-    internal abstract class JsonIEnumerableConverter<TCollection, TElement> : JsonResumableConverter<TCollection>
+    internal abstract class JsonCollectionConverter<TCollection, TElement> : JsonResumableConverter<TCollection>
     {
-        private Type _elementType = typeof(TElement);
-
-        internal override bool CanHaveValuesMetadata => true;
         internal override ClassType ClassType => ClassType.Enumerable;
-        internal override Type ElementType => _elementType;
+        internal override Type ElementType => typeof(TElement);
     }
 }
index dd8a660..da4021f 100644 (file)
@@ -12,6 +12,9 @@ namespace System.Text.Json.Serialization
     /// </summary>
     public abstract partial class JsonConverter
     {
+        /// <summary>
+        /// Perform a Read() and if read-ahead is required, also read-ahead (to the end of the current JSON level).
+        /// </summary>
         // AggressiveInlining used since this method is on a hot path and short. The optionally called
         // method DoSingleValueReadWithReadAhead is not inlined.
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -28,11 +31,10 @@ namespace System.Text.Json.Serialization
 
         internal static bool DoSingleValueReadWithReadAhead(ref Utf8JsonReader reader, ref ReadStack state)
         {
-            // When we're reading ahead we always have to save the state
-            // as we don't know if the next token is an opening object or
-            // array brace.
-            state.InitialReaderState = reader.CurrentState;
-            state.InitialReaderBytesConsumed = reader.BytesConsumed;
+            // When we're reading ahead we always have to save the state as we don't know if the next token
+            // is an opening object or an array brace.
+            JsonReaderState initialReaderState = reader.CurrentState;
+            long initialReaderBytesConsumed = reader.BytesConsumed;
 
             if (!reader.Read())
             {
@@ -47,15 +49,14 @@ namespace System.Text.Json.Serialization
                 bool complete = reader.TrySkip();
 
                 // We need to restore the state in all cases as we need to be positioned back before
-                // the current token to either attempt to skip again or to actually read the value in
-                // HandleValue below.
+                // the current token to either attempt to skip again or to actually read the value.
 
-                reader = new Utf8JsonReader(reader.OriginalSpan.Slice(checked((int)state.InitialReaderBytesConsumed)),
+                reader = new Utf8JsonReader(reader.OriginalSpan.Slice(checked((int)initialReaderBytesConsumed)),
                     isFinalBlock: reader.IsFinalBlock,
-                    state: state.InitialReaderState);
+                    state: initialReaderState);
 
                 Debug.Assert(reader.BytesConsumed == 0);
-                state.BytesConsumed += state.InitialReaderBytesConsumed;
+                state.BytesConsumed += initialReaderBytesConsumed;
 
                 if (!complete)
                 {
@@ -63,8 +64,8 @@ namespace System.Text.Json.Serialization
                     return false;
                 }
 
-                // Success, requeue the reader to the token for HandleValue.
-                reader.Read();
+                // Success, requeue the reader to the start token.
+                reader.ReadWithVerify();
                 Debug.Assert(tokenType == reader.TokenType);
             }
 
index eff19ae..1bc90ae 100644 (file)
@@ -37,17 +37,12 @@ namespace System.Text.Json.Serialization
         internal bool CanUseDirectReadOrWrite { get; set; }
 
         /// <summary>
-        /// Can the converter have any metadata (properties starting with $).
+        /// Can the converter have $id metadata.
         /// </summary>
-        internal virtual bool CanHaveMetadata => !TypeToConvert.IsValueType;
+        internal virtual bool CanHaveIdMetadata => true;
 
         internal bool CanBePolymorphic { get; set; }
 
-        /// <summary>
-        /// Can the converter have $values metadata.
-        /// </summary>
-        internal virtual bool CanHaveValuesMetadata => false;
-
         internal abstract JsonPropertyInfo CreateJsonPropertyInfo();
 
         internal abstract Type? ElementType { get; }
index 05eb5a1..352c32e 100644 (file)
@@ -19,7 +19,7 @@ namespace System.Text.Json.Serialization
         /// </summary>
         protected JsonConverterFactory() { }
 
-        internal override sealed ClassType ClassType
+        internal sealed override ClassType ClassType
         {
             get
             {
@@ -40,32 +40,41 @@ namespace System.Text.Json.Serialization
 
         internal override JsonPropertyInfo CreateJsonPropertyInfo()
         {
-            throw new NotSupportedException();
+            // We should never get here.
+            Debug.Assert(false);
+
+            throw new InvalidOperationException();
         }
 
         internal sealed override Type? ElementType => null;
 
-        internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
+        internal JsonConverter? GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
         {
             Debug.Assert(CanConvert(typeToConvert));
-            return CreateConverter(typeToConvert, options)!;
+            return CreateConverter(typeToConvert, options);
         }
 
-        internal override sealed bool TryReadAsObject(
+        internal sealed override bool TryReadAsObject(
             ref Utf8JsonReader reader,
             Type typeToConvert,
             JsonSerializerOptions options,
             ref ReadStack state,
             out object? value)
         {
-            throw new NotSupportedException();
+            // We should never get here.
+            Debug.Assert(false);
+
+            throw new InvalidOperationException();
         }
 
-        internal override sealed bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
+        internal sealed override bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
         {
-            throw new NotSupportedException();
+            // We should never get here.
+            Debug.Assert(false);
+
+            throw new InvalidOperationException();
         }
 
-        internal override sealed Type TypeToConvert => null!;
+        internal sealed override Type TypeToConvert => null!;
     }
 }
index f603b21..13ff86d 100644 (file)
@@ -12,8 +12,6 @@ namespace System.Text.Json.Serialization
     /// <typeparam name="T">The <see cref="Type"/> to convert.</typeparam>
     public abstract class JsonConverter<T> : JsonConverter
     {
-        private Type _typeToConvert = typeof(T);
-
         /// <summary>
         /// When overidden, constructs a new <see cref="JsonConverter{T}"/> instance.
         /// </summary>
@@ -42,7 +40,7 @@ namespace System.Text.Json.Serialization
 
         internal override ClassType ClassType => ClassType.Value;
 
-        internal override sealed JsonPropertyInfo CreateJsonPropertyInfo()
+        internal sealed override JsonPropertyInfo CreateJsonPropertyInfo()
         {
             return new JsonPropertyInfo<T>();
         }
@@ -52,10 +50,10 @@ namespace System.Text.Json.Serialization
         /// <summary>
         /// Is the converter built-in.
         /// </summary>
-        internal bool IsInternalConverter;
+        internal bool IsInternalConverter { get; set; }
 
         // This non-generic API is sealed as it just forwards to the generic version.
-        internal override sealed bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
+        internal sealed override bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state)
         {
             T valueOfT = (T)value!;
             return TryWrite(writer, valueOfT, options, ref state);
@@ -69,7 +67,7 @@ namespace System.Text.Json.Serialization
         }
 
         // This non-generic API is sealed as it just forwards to the generic version.
-        internal override sealed bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
+        internal sealed override bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
         {
             bool success = TryRead(ref reader, typeToConvert, options, ref state, out T valueOfT);
             if (success)
@@ -126,15 +124,16 @@ namespace System.Text.Json.Serialization
                 else
 #endif
                 {
-                    state.Current.OriginalPropertyTokenType = reader.TokenType;
-                    state.Current.OriginalPropertyDepth = reader.CurrentDepth;
-                    state.Current.OriginalPropertyBytesConsumed = reader.BytesConsumed;
+                    JsonTokenType originalPropertyTokenType = reader.TokenType;
+                    int originalPropertyDepth = reader.CurrentDepth;
+                    long originalPropertyBytesConsumed = reader.BytesConsumed;
 
                     value = Read(ref reader, typeToConvert, options);
                     VerifyRead(
-                        state.Current.OriginalPropertyTokenType,
-                        state.Current.OriginalPropertyDepth,
-                        state.Current.OriginalPropertyBytesConsumed != reader.BytesConsumed,
+                        originalPropertyTokenType,
+                        originalPropertyDepth,
+                        originalPropertyBytesConsumed,
+                        isValueConverter: true,
                         ref reader);
                 }
 
@@ -195,7 +194,8 @@ namespace System.Text.Json.Serialization
                     VerifyRead(
                         state.Current.OriginalTokenType,
                         state.Current.OriginalDepth,
-                        hasConsumedAnyBytes: true,
+                        bytesConsumed : 0,
+                        isValueConverter: false,
                         ref reader);
 
                     // No need to clear state.Current.* since a stack pop will occur.
@@ -231,6 +231,7 @@ namespace System.Text.Json.Serialization
 
                 if (type != TypeToConvert)
                 {
+                    // Handle polymorphic case and get the new converter.
                     JsonConverter jsonConverter = state.Current.InitializeReEntry(type, options);
                     if (jsonConverter != this)
                     {
@@ -242,13 +243,12 @@ namespace System.Text.Json.Serialization
 
             if (ClassType == ClassType.Value)
             {
-                if (!state.IsContinuation)
-                {
-                    state.Current.OriginalPropertyDepth = writer.CurrentDepth;
-                }
+                Debug.Assert(!state.IsContinuation);
+
+                int originalPropertyDepth = writer.CurrentDepth;
 
                 Write(writer, value, options);
-                VerifyWrite(state.Current.OriginalPropertyDepth, writer);
+                VerifyWrite(originalPropertyDepth, writer);
 
                 return true;
             }
@@ -289,10 +289,9 @@ namespace System.Text.Json.Serialization
 
             if (ClassType == ClassType.Value)
             {
-                if (!state.IsContinuation)
-                {
-                    state.Current.OriginalPropertyDepth = writer.CurrentDepth;
-                }
+                Debug.Assert(!state.IsContinuation);
+
+                int originalPropertyDepth = writer.CurrentDepth;
 
                 // Ignore the naming policy for extension data.
                 state.Current.IgnoreDictionaryKeyPolicy = true;
@@ -300,7 +299,7 @@ namespace System.Text.Json.Serialization
                 success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
                 if (success)
                 {
-                    VerifyWrite(state.Current.OriginalPropertyDepth, writer);
+                    VerifyWrite(originalPropertyDepth, writer);
                 }
             }
             else
@@ -330,9 +329,9 @@ namespace System.Text.Json.Serialization
             return success;
         }
 
-        internal override sealed Type TypeToConvert => _typeToConvert;
+        internal sealed override Type TypeToConvert => typeof(T);
 
-        internal void VerifyRead(JsonTokenType tokenType, int depth, bool hasConsumedAnyBytes, ref Utf8JsonReader reader)
+        internal void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, bool isValueConverter, ref Utf8JsonReader reader)
         {
             switch (tokenType)
             {
@@ -361,8 +360,9 @@ namespace System.Text.Json.Serialization
                     break;
 
                 default:
-                    // A non-array or non-object should not make any additional reads.
-                    if (hasConsumedAnyBytes)
+                    // A non-value converter (object or collection) should always have Start and End tokens.
+                    // A value converter should not make any reads.
+                    if (!isValueConverter || reader.BytesConsumed != bytesConsumed)
                     {
                         ThrowHelper.ThrowJsonException_SerializationConverterRead(this);
                     }
index a1eb390..86965da 100644 (file)
@@ -11,6 +11,6 @@ namespace System.Text.Json.Serialization
     internal abstract class JsonObjectConverter<T> : JsonResumableConverter<T>
     {
         internal override ClassType ClassType => ClassType.Object;
-        internal override sealed Type? ElementType => null;
+        internal sealed override Type? ElementType => null;
     }
 }
index 0990238..5e45f08 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json.Serialization;
 
@@ -105,10 +106,7 @@ namespace System.Text.Json
             {
                 if (HasGetter)
                 {
-                    if (ConverterBase == null)
-                    {
-                        ThrowCollectionNotSupportedException();
-                    }
+                    Debug.Assert(ConverterBase != null);
 
                     ShouldSerialize = true;
 
@@ -153,6 +151,8 @@ namespace System.Text.Json
             JsonConverter converter,
             JsonSerializerOptions options)
         {
+            Debug.Assert(converter != null);
+
             ParentClassType = parentClassType;
             DeclaredPropertyType = declaredPropertyType;
             RuntimePropertyType = runtimePropertyType;
@@ -171,7 +171,7 @@ namespace System.Text.Json
 
         // The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute.
         public byte[]? Name { get; private set; }
-        public string? NameAsString { get; set; }
+        public string? NameAsString { get; private set; }
 
         // Key for fast property name lookup.
         public ulong PropertyNameKey { get; set; }
@@ -182,31 +182,45 @@ namespace System.Text.Json
         public bool ReadJsonAndAddExtensionProperty(object obj, ref ReadStack state, ref Utf8JsonReader reader)
         {
             object propValue = GetValueAsObject(obj)!;
-            IDictionary<string, object?>? dictionaryObject = propValue as IDictionary<string, object?>;
 
-            if (dictionaryObject != null && reader.TokenType == JsonTokenType.Null)
+            if (propValue is IDictionary<string, object?> dictionaryObject)
             {
-                // A null JSON value is treated as a null object reference.
-                dictionaryObject[state.Current.KeyName!] = null;
+                // Handle case where extension property is System.Object-based.
+
+                if (reader.TokenType == JsonTokenType.Null)
+                {
+                    // A null JSON value is treated as a null object reference.
+                    dictionaryObject[state.Current.JsonPropertyNameAsString!] = null;
+                }
+                else
+                {
+                    JsonConverter<object> converter = (JsonConverter<object>)
+                        state.Current.JsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+
+                    if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out object? value))
+                    {
+                        return false;
+                    }
+
+                    dictionaryObject[state.Current.JsonPropertyNameAsString!] = value;
+                }
             }
             else
             {
-                JsonConverter<JsonElement> converter = JsonSerializerOptions.GetJsonElementConverter();
-                if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement jsonElement))
+                // Handle case where extension property is JsonElement-based.
+
+                Debug.Assert(propValue is IDictionary<string, JsonElement>);
+                IDictionary<string, JsonElement> dictionaryJsonElement = (IDictionary<string, JsonElement>)propValue;
+
+                JsonConverter<JsonElement> converter = (JsonConverter<JsonElement>)
+                    state.Current.JsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty!.ConverterBase;
+
+                if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement value))
                 {
-                    // No need to set a partial object here since JsonElement is a struct that must be read in full.
                     return false;
                 }
 
-                if (dictionaryObject != null)
-                {
-                    dictionaryObject[state.Current.KeyName!] = jsonElement;
-                }
-                else
-                {
-                    IDictionary<string, JsonElement> dictionaryJsonElement = (IDictionary<string, JsonElement>)propValue;
-                    dictionaryJsonElement[state.Current.KeyName!] = jsonElement;
-                }
+                dictionaryJsonElement[state.Current.JsonPropertyNameAsString!] = value;
             }
 
             return true;
@@ -237,10 +251,5 @@ namespace System.Text.Json
 
         public bool ShouldSerialize { get; private set; }
         public bool ShouldDeserialize { get; private set; }
-
-        public void ThrowCollectionNotSupportedException()
-        {
-            throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(RuntimePropertyType!, ParentClassType, PropertyInfo);
-        }
     }
 }
index 691fcfc..252f844 100644 (file)
@@ -104,8 +104,6 @@ namespace System.Text.Json
                 if (state.Current.PropertyState < StackFramePropertyState.Name)
                 {
                     state.Current.PropertyState = StackFramePropertyState.Name;
-
-                    Debug.Assert(EscapedName.HasValue);
                     writer.WritePropertyName(EscapedName.Value);
                 }
 
@@ -126,7 +124,7 @@ namespace System.Text.Json
             }
             else
             {
-                state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo.RuntimeClassInfo.ElementClassInfo!.PolicyProperty;
+                state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PolicyProperty;
                 success = Converter.TryWriteDataExtensionProperty(writer, value, Options, ref state);
             }
 
@@ -149,9 +147,10 @@ namespace System.Text.Json
             }
             else
             {
-                // Optimize for internal converters by avoiding the extra call to TryRead.
+                // Get the value from the converter and set the property.
                 if (Converter.CanUseDirectReadOrWrite)
                 {
+                    // Optimize for internal converters by avoiding the extra call to TryRead.
                     TConverter fastvalue = Converter.Read(ref reader, RuntimePropertyType!, Options);
                     if (!IgnoreNullValues || (!isNullToken && fastvalue != null))
                     {
index 23097ae..205b45f 100644 (file)
@@ -16,7 +16,7 @@ namespace System.Text.Json.Serialization
             // Bridge from resumable to value converters.
             if (options == null)
             {
-                options = JsonSerializerOptions.s_defaultOptions;
+                throw new ArgumentNullException(nameof(options));
             }
 
             ReadStack state = default;
@@ -30,11 +30,11 @@ namespace System.Text.Json.Serialization
             // Bridge from resumable to value converters.
             if (options == null)
             {
-                options = JsonSerializerOptions.s_defaultOptions;
+                throw new ArgumentNullException(nameof(options));
             }
 
             WriteStack state = default;
-            state.InitializeRoot(typeof(T), options);
+            state.InitializeRoot(typeof(T), options, supportContinuation: false);
             TryWrite(writer, value, options, ref state);
         }
     }
index e798b45..206de8d 100644 (file)
@@ -9,64 +9,73 @@ namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        internal static bool ResolveMetadata<T>(
-            this JsonConverter converter,
+        /// <summary>
+        /// Returns true if successful, false is the reader ran out of buffer.
+        /// Sets state.Current.ReturnValue to the $ref target for MetadataRefProperty cases.
+        /// </summary>
+        internal static bool ResolveMetadata(
+            JsonConverter converter,
             ref Utf8JsonReader reader,
-            ref ReadStack state,
-            out T value)
+            ref ReadStack state)
         {
             if (state.Current.ObjectState < StackFrameObjectState.MetadataPropertyName)
             {
                 // Read the first metadata property name.
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
                 if (reader.TokenType != JsonTokenType.PropertyName)
                 {
+                    // An enumerable needs metadata since it starts with StartObject.
                     if (converter.ClassType == ClassType.Enumerable)
                     {
                         ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
                     }
-                    else
-                    {
-                        ThrowHelper.ThrowJsonException_MetadataIdIsNotFirstProperty();
-                    }
+
+                    // The reader should have detected other invalid cases.
+                    Debug.Assert(reader.TokenType == JsonTokenType.EndObject);
+
+                    // Skip the read of the first property name, since we already read it above.
+                    state.Current.PropertyState = StackFramePropertyState.ReadName;
+
+                    return true;
                 }
 
-                ReadOnlySpan<byte> propertyName = GetSpan(ref reader);
+                ReadOnlySpan<byte> propertyName = reader.GetSpan();
                 MetadataPropertyName metadata = GetMetadataPropertyName(propertyName);
-                state.Current.MetadataPropertyName = metadata;
-                if (metadata == MetadataPropertyName.Ref)
+                if (metadata == MetadataPropertyName.Id)
                 {
-                    if (!converter.CanHaveMetadata)
+                    state.Current.JsonPropertyName = propertyName.ToArray();
+                    if (!converter.CanHaveIdMetadata)
                     {
-                        ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+                        ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert);
                     }
 
-                    state.Current.ObjectState = StackFrameObjectState.MetadataRefProperty;
+                    state.Current.ObjectState = StackFrameObjectState.MetadataIdProperty;
                 }
-                else if (metadata == MetadataPropertyName.Id)
+                else if (metadata == MetadataPropertyName.Ref)
                 {
-                    if (!converter.CanHaveMetadata)
+                    state.Current.JsonPropertyName = propertyName.ToArray();
+                    if (converter.TypeToConvert.IsValueType)
                     {
-                        if ((converter.ClassType & (ClassType.Dictionary | ClassType.Enumerable)) != 0)
-                        {
-                            ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert);
-                        }
-                        else
-                        {
-                            ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(state.Current.JsonClassInfo.Type);
-                        }
+                        ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert);
                     }
 
-                    state.Current.ObjectState = StackFrameObjectState.MetadataIdProperty;
+                    state.Current.ObjectState = StackFrameObjectState.MetadataRefProperty;
                 }
                 else if (metadata == MetadataPropertyName.Values)
                 {
-                    ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues();
+                    state.Current.JsonPropertyName = propertyName.ToArray();
+                    if (converter.ClassType == ClassType.Enumerable)
+                    {
+                        ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues();
+                    }
+                    else
+                    {
+                        ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+                    }
                 }
                 else
                 {
@@ -75,12 +84,12 @@ namespace System.Text.Json
                     // Having a StartObject without metadata properties is not allowed.
                     if (converter.ClassType == ClassType.Enumerable)
                     {
-                        ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader, ref state);
+                        state.Current.JsonPropertyName = propertyName.ToArray();
+                        ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader);
                     }
 
                     // Skip the read of the first property name, since we already read it above.
                     state.Current.PropertyState = StackFramePropertyState.ReadName;
-                    value = default!;
                     return true;
                 }
             }
@@ -89,7 +98,6 @@ namespace System.Text.Json
             {
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
@@ -100,14 +108,14 @@ namespace System.Text.Json
 
                 string key = reader.GetString()!;
 
-                state.Current.ReturnValue = state.ReferenceResolver.ResolveReferenceOnDeserialize(key!)!;
+                // todo: verify value is converter.TypeToConvert and throw JsonException? (currently no test)
+                state.Current.ReturnValue = state.ReferenceResolver.ResolveReferenceOnDeserialize(key);
                 state.Current.ObjectState = StackFrameObjectState.MetadataRefPropertyEndObject;
             }
             else if (state.Current.ObjectState == StackFrameObjectState.MetadataIdProperty)
             {
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
@@ -116,13 +124,12 @@ namespace System.Text.Json
                     ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType);
                 }
 
-                string id = reader.GetString()!;
-                state.Current.MetadataId = id;
+                state.Current.MetadataId = reader.GetString();
 
                 // Clear the MetadataPropertyName since we are done processing Id.
-                state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
+                state.Current.JsonPropertyName = default;
 
-                if (converter.ClassType == ClassType.Enumerable && converter.CanHaveValuesMetadata)
+                if (converter.ClassType == ClassType.Enumerable)
                 {
                     // Need to Read $values property name.
                     state.Current.ObjectState = StackFrameObjectState.MetadataValuesPropertyName;
@@ -130,7 +137,8 @@ namespace System.Text.Json
                 else
                 {
                     // We are done reading metadata.
-                    state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+                    state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
+                    return true;
                 }
             }
 
@@ -138,19 +146,17 @@ namespace System.Text.Json
             {
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
                 if (reader.TokenType != JsonTokenType.EndObject)
                 {
-                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(converter.TypeToConvert);
-                }
+                    // We just read a property. The only valid next tokens are EndObject and PropertyName.
+                    Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 
-                // Clear the MetadataPropertyName since we are done processing Ref.
-                state.Current.MetadataPropertyName = MetadataPropertyName.NoMetadata;
+                    ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state);
+                }
 
-                value = (T)state.Current.ReturnValue!;
                 return true;
             }
 
@@ -158,7 +164,6 @@ namespace System.Text.Json
             {
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
@@ -167,12 +172,16 @@ namespace System.Text.Json
                     ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
                 }
 
-                if (reader.GetString() != "$values")
+                ReadOnlySpan<byte> propertyName = reader.GetSpan();
+
+                // Remember the property in case we get an exception.
+                state.Current.JsonPropertyName = propertyName.ToArray();
+
+                if (GetMetadataPropertyName(propertyName) != MetadataPropertyName.Values)
                 {
-                    ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
+                    ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader);
                 }
 
-                state.Current.MetadataPropertyName = MetadataPropertyName.Values;
                 state.Current.ObjectState = StackFrameObjectState.MetadataValuesPropertyStartArray;
             }
 
@@ -180,39 +189,20 @@ namespace System.Text.Json
             {
                 if (!reader.Read())
                 {
-                    value = default!;
                     return false;
                 }
 
                 if (reader.TokenType != JsonTokenType.StartArray)
                 {
-                    ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert);
+                    ThrowHelper.ThrowJsonException_MetadataValuesInvalidToken(reader.TokenType);
                 }
 
-                state.Current.ObjectState = StackFrameObjectState.MetataPropertyValue;
+                state.Current.ObjectState = StackFrameObjectState.MetadataPropertyValue;
             }
 
-            value = default!;
             return true;
         }
 
-        internal static string? GetMetadataPropertyName(in ReadStackFrame frame)
-        {
-            switch (frame.MetadataPropertyName)
-            {
-                case MetadataPropertyName.Id:
-                    return "$id";
-
-                case MetadataPropertyName.Ref:
-                    return "$ref";
-
-                case MetadataPropertyName.Values:
-                    return "$values";
-            }
-
-            return null;
-        }
-
         internal static MetadataPropertyName GetMetadataPropertyName(ReadOnlySpan<byte> propertyName)
         {
             if (propertyName.Length > 0 && propertyName[0] == '$')
index 62a65e9..7ecdf4c 100644 (file)
@@ -5,41 +5,52 @@
 using System.Collections;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
 
 namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
         /// <summary>
-        /// Lookup the property given its name in the reader.
+        /// Lookup the property given its name (obtained from the reader) and return it.
+        /// Also sets state.Current.JsonPropertyInfo to a non-null value.
         /// </summary>
         // AggressiveInlining used although a large method it is only called from two locations and is on a hot path.
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static void LookupProperty(
+        internal static JsonPropertyInfo LookupProperty(
             object obj,
             ref Utf8JsonReader reader,
             JsonSerializerOptions options,
             ref ReadStack state,
-            out JsonPropertyInfo jsonPropertyInfo,
             out bool useExtensionProperty)
         {
             Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
 
-            ReadOnlySpan<byte> unescapedPropertyName = GetSpan(ref reader);
-            ReadOnlySpan<byte> propertyName;
+            JsonPropertyInfo jsonPropertyInfo;
+
+            ReadOnlySpan<byte> unescapedPropertyName;
+            ReadOnlySpan<byte> propertyName = reader.GetSpan();
 
             if (reader._stringHasEscaping)
             {
-                int idx = unescapedPropertyName.IndexOf(JsonConstants.BackSlash);
+                int idx = propertyName.IndexOf(JsonConstants.BackSlash);
                 Debug.Assert(idx != -1);
-                propertyName = GetUnescapedString(unescapedPropertyName, idx);
+                unescapedPropertyName = GetUnescapedString(propertyName, idx);
             }
             else
             {
-                propertyName = unescapedPropertyName;
+                unescapedPropertyName = propertyName;
+            }
+
+            if (options.ReferenceHandling.ShouldReadPreservedReferences())
+            {
+                if (propertyName.Length > 0 && propertyName[0] == '$')
+                {
+                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
+                }
             }
 
-            jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current);
+            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++;
@@ -47,42 +58,30 @@ namespace System.Text.Json
             // Determine if we should use the extension property.
             if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
             {
-                if (options.ReferenceHandling.ShouldReadPreservedReferences())
-                {
-                    if (unescapedPropertyName.Length > 0 && unescapedPropertyName[0] == '$')
-                    {
-                        // Ensure JsonPath doesn't attempt to use the previous property.
-                        state.Current.JsonPropertyInfo = null!;
-
-                        ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(unescapedPropertyName, ref state, reader);
-                    }
-                }
-
                 JsonPropertyInfo? dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
                 if (dataExtProperty != null)
                 {
-                    state.Current.JsonPropertyName = propertyName.ToArray();
-                    state.Current.KeyName = JsonHelpers.Utf8GetString(propertyName);
+                    state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
                     CreateDataExtensionProperty(obj, dataExtProperty);
                     jsonPropertyInfo = dataExtProperty;
                 }
 
                 state.Current.JsonPropertyInfo = jsonPropertyInfo;
                 useExtensionProperty = true;
-                return;
+                return jsonPropertyInfo;
             }
 
             // Support JsonException.Path.
             Debug.Assert(
                 jsonPropertyInfo.JsonPropertyName == null ||
                 options.PropertyNameCaseInsensitive ||
-                propertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));
+                unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));
 
             state.Current.JsonPropertyInfo = jsonPropertyInfo;
 
             if (jsonPropertyInfo.JsonPropertyName == null)
             {
-                byte[] propertyNameArray = propertyName.ToArray();
+                byte[] propertyNameArray = unescapedPropertyName.ToArray();
                 if (options.PropertyNameCaseInsensitive)
                 {
                     // Each payload can have a different name here; remember the value on the temporary stack.
@@ -98,9 +97,10 @@ namespace System.Text.Json
 
             state.Current.JsonPropertyInfo = jsonPropertyInfo;
             useExtensionProperty = false;
+            return jsonPropertyInfo;
         }
 
-        internal static void CreateDataExtensionProperty(
+        private static void CreateDataExtensionProperty(
             object obj,
             JsonPropertyInfo jsonPropertyInfo)
         {
index 3b751c7..1c5113b 100644 (file)
@@ -2,20 +2,10 @@
 // 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.Runtime.CompilerServices;
-
 namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static ReadOnlySpan<byte> GetSpan(ref Utf8JsonReader reader)
-        {
-            ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
-            return propertyName;
-        }
-
         private static object? ReadCore(
             Type returnType,
             JsonSerializerOptions options,
index 38a018a..3f2234a 100644 (file)
@@ -218,7 +218,6 @@ namespace System.Text.Json
             // to enable read ahead behaviors to ensure we have complete json objects and arrays
             // ({}, []) when needed. (Notably to successfully parse JsonElement via JsonDocument
             // to assign to object and JsonElement properties in the constructed .NET object.)
-            //state.SetToTop();
             state.ReadAhead = !isFinalBlock;
             state.BytesConsumed = 0;
 
index 48bc391..df534fa 100644 (file)
@@ -22,7 +22,12 @@ namespace System.Text.Json
 
             state.Current.InitializeReEntry(typeof(T), options, propertyName);
 
-            return (T)ReadCoreReEntry(options, ref reader, ref state)!;
+            T value = (T)ReadCoreReEntry(options, ref reader, ref state)!;
+
+            // Clear the current property state since we are done processing it.
+            state.Current.EndProperty();
+
+            return value;
         }
 
         /// <summary>
@@ -316,7 +321,7 @@ namespace System.Text.Json
                     valueSpan.CopyTo(rentedSpan);
                 }
 
-                JsonReaderOptions originalReaderOptions = state.Options;
+                JsonReaderOptions originalReaderOptions = readerState.Options;
 
                 var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions);
 
index b1e7f6c..50deaa6 100644 (file)
@@ -14,7 +14,7 @@ namespace System.Text.Json
     /// </summary>
     public static partial class JsonSerializer
     {
-        internal static void ReadCore(
+        private static void ReadCore(
             JsonSerializerOptions options,
             ref Utf8JsonReader reader,
             ref ReadStack state)
@@ -35,8 +35,8 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    // For a continuation continue to read ahead here to avoid having to build and
-                    // then tear down the call stack if there is more than one buffer fetch necessary.
+                    // For a continuation, read ahead here to avoid having to build and then tear
+                    // down the call stack if there is more than one buffer fetch necessary.
                     if (!JsonConverter.SingleValueReadWithReadAhead(ClassType.Value, ref reader, ref state))
                     {
                         state.BytesConsumed += reader.BytesConsumed;
@@ -81,7 +81,7 @@ namespace System.Text.Json
             ref Utf8JsonReader reader,
             ref ReadStack state)
         {
-            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo!;
             JsonConverter converter = jsonPropertyInfo.ConverterBase;
             bool success = converter.TryReadAsObject(ref reader, jsonPropertyInfo.RuntimePropertyType!, options, ref state, out object? value);
             Debug.Assert(success);
index 2235ae3..151e532 100644 (file)
@@ -10,9 +10,29 @@ namespace System.Text.Json
     public static partial class JsonSerializer
     {
         // Pre-encoded metadata properties.
-        private static readonly JsonEncodedText s_metadataId = JsonEncodedText.Encode("$id", encoder: null);
-        private static readonly JsonEncodedText s_metadataRef = JsonEncodedText.Encode("$ref", encoder: null);
-        private static readonly JsonEncodedText s_metadataValues = JsonEncodedText.Encode("$values", encoder: null);
+        internal static readonly JsonEncodedText s_metadataId = JsonEncodedText.Encode("$id", encoder: null);
+        internal static readonly JsonEncodedText s_metadataRef = JsonEncodedText.Encode("$ref", encoder: null);
+        internal static readonly JsonEncodedText s_metadataValues = JsonEncodedText.Encode("$values", encoder: null);
+
+        internal static MetadataPropertyName GetResolvedReferenceHandling(
+            JsonConverter converter,
+            object value,
+            ref WriteStack state,
+            out string? referenceId)
+        {
+            if (!converter.CanHaveIdMetadata || converter.TypeToConvert.IsValueType)
+            {
+                referenceId = default;
+                return MetadataPropertyName.NoMetadata;
+            }
+
+            if (state.ReferenceResolver.TryGetOrAddReferenceOnSerialize(value, out referenceId))
+            {
+                return MetadataPropertyName.Ref;
+            }
+
+            return MetadataPropertyName.Id;
+        }
 
         internal static MetadataPropertyName WriteReferenceForObject(
             JsonConverter jsonConverter,
@@ -20,11 +40,12 @@ namespace System.Text.Json
             ref WriteStack state,
             Utf8JsonWriter writer)
         {
-            MetadataPropertyName metadataToWrite = state.GetResolvedReferenceHandling(jsonConverter, currentValue, out string? referenceId);
+            MetadataPropertyName metadataToWrite = GetResolvedReferenceHandling(jsonConverter, currentValue, ref state, out string? referenceId);
 
             if (metadataToWrite == MetadataPropertyName.Ref)
             {
                 writer.WriteString(s_metadataRef, referenceId!);
+                writer.WriteEndObject();
             }
             else if (metadataToWrite == MetadataPropertyName.Id)
             {
@@ -40,7 +61,7 @@ namespace System.Text.Json
             ref WriteStack state,
             Utf8JsonWriter writer)
         {
-            MetadataPropertyName metadataToWrite = state.GetResolvedReferenceHandling(jsonConverter, currentValue, out string? referenceId);
+            MetadataPropertyName metadataToWrite = GetResolvedReferenceHandling(jsonConverter, currentValue, ref state, out string? referenceId);
 
             if (metadataToWrite == MetadataPropertyName.NoMetadata)
             {
@@ -50,8 +71,7 @@ namespace System.Text.Json
             {
                 writer.WriteStartObject();
                 writer.WriteString(s_metadataId, referenceId!);
-                writer.WritePropertyName(s_metadataValues);
-                writer.WriteStartArray();
+                writer.WriteStartArray(s_metadataValues);
             }
             else
             {
index eafa2cd..31d2b21 100644 (file)
@@ -101,7 +101,7 @@ namespace System.Text.Json
                 }
 
                 WriteStack state = default;
-                state.InitializeRoot(type!, options);
+                state.InitializeRoot(type!, options, supportContinuation: false);
                 WriteCore(writer, value, options, ref state, state.Current.JsonClassInfo!.PolicyProperty!.ConverterBase);
             }
 
index 9eea08c..fb42a2b 100644 (file)
@@ -71,10 +71,7 @@ namespace System.Text.Json
                 }
 
                 WriteStack state = default;
-                state.InitializeRoot(inputType, options);
-
-                // Ensures converters support contination due to having to re-populate the buffer from a Stream.
-                state.SupportContinuation = true;
+                state.InitializeRoot(inputType, options, supportContinuation: true);
 
                 bool isFinalBlock;
 
index 7c9b0f9..bf31e42 100644 (file)
@@ -3,14 +3,16 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Text.Json.Serialization;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
         /// <summary>
-        /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly.
+        /// Internal version that allows re-entry with preserving WriteStack so that JsonPath works correctly.
         /// </summary>
+        // If this is made public, we will also want to have a non-generic version.
         internal static void Serialize<T>(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state, string? propertyName = null)
         {
             if (options == null)
@@ -19,26 +21,8 @@ namespace System.Text.Json
             }
 
             JsonConverter jsonConverter = state.Current.InitializeReEntry(typeof(T), options, propertyName);
-            Write(writer, value, options, ref state, jsonConverter);
-        }
-
-        /// <summary>
-        /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly.
-        /// </summary>
-        internal static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerOptions options, ref WriteStack state, string? propertyName = null)
-        {
-            if (inputType == null)
-            {
-                throw new ArgumentNullException(nameof(inputType));
-            }
-
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            JsonConverter jsonConverter = state.Current.InitializeReEntry(inputType, options, propertyName);
-            Write(writer, value, options, ref state, jsonConverter);
+            bool success = jsonConverter.TryWriteAsObject(writer, value, options, ref state);
+            Debug.Assert(success);
         }
 
         /// <summary>
index a65f38e..a1999ac 100644 (file)
@@ -21,12 +21,12 @@ namespace System.Text.Json
         {
             try
             {
-                return Write(writer, value, options, ref state, jsonConverter);
+                return jsonConverter.TryWriteAsObject(writer, value, options, ref state);
             }
             catch (InvalidOperationException ex) when (ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException)
             {
                 ThrowHelper.ReThrowWithPath(state, ex);
-                return default;
+                throw;
             }
             catch (JsonException ex)
             {
@@ -34,15 +34,5 @@ namespace System.Text.Json
                 throw;
             }
         }
-
-        private static bool Write(
-            Utf8JsonWriter writer,
-            object? value,
-            JsonSerializerOptions options,
-            ref WriteStack state,
-            JsonConverter jsonConverter)
-        {
-            return jsonConverter.TryWriteAsObject(writer, value, options, ref state);
-        }
     }
 }
index d9f7512..227a5b8 100644 (file)
@@ -53,10 +53,11 @@ namespace System.Text.Json
             converters.Add(new JsonConverterEnum());
             converters.Add(new JsonKeyValuePairConverter());
 
-            // Enumerable and Object converters should always be last since they can convert any IEnumerable
-            // or non-IEnumerable.
+            // IEnumerable should always be last since they can convert any IEnumerable.
             converters.Add(new JsonIEnumerableConverterFactory());
-            converters.Add(new JsonObjectFactoryConverter());
+
+            // Object should always be last since it converts any type.
+            converters.Add(new JsonObjectConverterFactory());
 
             Debug.Assert(NumberOfConverters == converters.Count);
 
@@ -188,7 +189,6 @@ namespace System.Text.Json
             return converter;
         }
 
-
         private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converterAttribute, Type typeToConvert, Type classTypeAttributeIsOn, PropertyInfo? propertyInfo)
         {
             JsonConverter? converter;
index fe45cf4..9497f30 100644 (file)
@@ -6,8 +6,6 @@ using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Text.Json.Serialization;
 using System.Text.Encodings.Web;
-using System.Text.Json.Serialization.Converters;
-using System.Runtime.CompilerServices;
 
 namespace System.Text.Json
 {
@@ -29,10 +27,6 @@ namespace System.Text.Json
         private ReferenceHandling _referenceHandling = ReferenceHandling.Default;
         private JavaScriptEncoder? _encoder = null;
 
-        // Cache common converters.
-        private static JsonConverter<object> s_objectConverter = null!;
-        private static JsonConverter<JsonElement> s_jsonElementConverter = null!;
-
         private int _defaultBufferSize = BufferSizeDefault;
         private int _maxDepth;
         private bool _allowTrailingCommas;
@@ -324,14 +318,11 @@ namespace System.Text.Json
             {
                 if (_memberAccessorStrategy == null)
                 {
-                    if (RuntimeFeature.IsDynamicCodeSupported)
-                    {
-                        _memberAccessorStrategy = new ReflectionEmitMemberAccessor();
-                    }
-                    else
-                    {
-                        _memberAccessorStrategy = new ReflectionMemberAccessor();
-                    }
+#if NETFRAMEWORK || NETCOREAPP
+                    _memberAccessorStrategy = new ReflectionEmitMemberAccessor();
+#else
+                    _memberAccessorStrategy = new ReflectionMemberAccessor();
+#endif
                 }
 
                 return _memberAccessorStrategy;
@@ -383,25 +374,5 @@ namespace System.Text.Json
                 ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable();
             }
         }
-
-        internal static JsonConverter<JsonElement> GetJsonElementConverter()
-        {
-            if (s_jsonElementConverter == null)
-            {
-                s_jsonElementConverter = new JsonConverterJsonElement();
-            }
-
-            return s_jsonElementConverter;
-        }
-
-        internal static JsonConverter<object> GetObjectConverter()
-        {
-            if (s_objectConverter == null)
-            {
-                s_objectConverter = new JsonConverterObject();
-            }
-
-            return s_objectConverter;
-        }
     }
 }
index 8dc566a..15a1e24 100644 (file)
@@ -4,11 +4,11 @@
 
 namespace System.Text.Json.Serialization
 {
-    // Used for value converters that need to re-enter the serializer since it will
-    // support JsonPath and other features that require per-serialization state.
+    // Used for value converters that need to re-enter the serializer since it will support JsonPath
+    // and reference handling.
     internal abstract class JsonValueConverter<T> : JsonConverter<T>
     {
-        internal override sealed ClassType ClassType => ClassType.NewValue;
+        internal sealed override ClassType ClassType => ClassType.NewValue;
 
         public override sealed T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
         {
@@ -33,7 +33,7 @@ namespace System.Text.Json.Serialization
             }
 
             WriteStack state = default;
-            state.InitializeRoot(typeof(T), options);
+            state.InitializeRoot(typeof(T), options, supportContinuation: false);
             TryWrite(writer, value, options, ref state);
         }
     }
index 2ed74e5..5a30856 100644 (file)
@@ -11,7 +11,7 @@ namespace System.Text.Json
     {
         public abstract JsonClassInfo.ConstructorDelegate? CreateConstructor(Type classType);
 
-        public abstract Action<TCollection, object> CreateAddMethodDelegate<TCollection>();
+        public abstract Action<TCollection, object?> CreateAddMethodDelegate<TCollection>();
 
         public abstract Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TElement, TCollection>();
 
index fa94a5e..0c8aa50 100644 (file)
@@ -34,10 +34,6 @@ namespace System.Text.Json
         // A field is used instead of a property to avoid value semantics.
         public ReadStackFrame Current;
 
-        // Support the read-ahead feature.
-        public JsonReaderState InitialReaderState;
-        public long InitialReaderBytesConsumed;
-
         public bool IsContinuation => _continuationCount != 0;
         public bool IsLastContinuation => _continuationCount == _count;
 
@@ -106,7 +102,7 @@ namespace System.Text.Json
                     if ((Current.JsonClassInfo.ClassType & (ClassType.Object | ClassType.Value | ClassType.NewValue)) != 0)
                     {
                         // Although ClassType.Value doesn't push, a custom custom converter may re-enter serialization.
-                        jsonClassInfo = Current.JsonPropertyInfo.RuntimeClassInfo;
+                        jsonClassInfo = Current.JsonPropertyInfo!.RuntimeClassInfo;
                     }
                     else
                     {
@@ -156,7 +152,6 @@ namespace System.Text.Json
                     {
                         // No need for a continuation since there is only one stack frame.
                         _continuationCount = 1;
-                        _count = 1;
                     }
                     else
                     {
@@ -215,36 +210,26 @@ namespace System.Text.Json
 
             return sb.ToString();
 
-            void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame)
+            static void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame)
             {
                 // Append the property name.
                 string? propertyName = GetPropertyName(frame);
                 AppendPropertyName(sb, propertyName);
 
-                // For metadata properties, include the name.
-                propertyName = JsonSerializer.GetMetadataPropertyName(in frame);
-                AppendPropertyName(sb, propertyName);
-
-                if (frame.JsonClassInfo != null)
+                if (frame.JsonClassInfo != null && frame.IsProcessingEnumerable())
                 {
-                    if (frame.IsProcessingDictionary())
+                    IEnumerable? enumerable = (IEnumerable?)frame.ReturnValue;
+                    if (enumerable == null)
                     {
-                        // For dictionaries add the key.
-                        AppendPropertyName(sb, frame.KeyName);
+                        return;
                     }
-                    else if (frame.IsProcessingEnumerable())
+
+                    // Once all elements are read, the exception is not within the array.
+                    if (frame.ObjectState < StackFrameObjectState.ReadElements)
                     {
-                        IEnumerable enumerable = (IEnumerable)frame.ReturnValue!;
-                        if (enumerable != null)
-                        {
-                            // Once all elements are read, the exception is not within the array.
-                            if (frame.ObjectState < StackFrameObjectState.ReadElements)
-                            {
-                                sb.Append(@"[");
-                                sb.Append(GetCount(enumerable));
-                                sb.Append(@"]");
-                            }
-                        }
+                        sb.Append(@"[");
+                        sb.Append(GetCount(enumerable));
+                        sb.Append(@"]");
                     }
                 }
             }
@@ -266,7 +251,7 @@ namespace System.Text.Json
                 return count;
             }
 
-            void AppendPropertyName(StringBuilder sb, string? propertyName)
+            static void AppendPropertyName(StringBuilder sb, string? propertyName)
             {
                 if (propertyName != null)
                 {
@@ -284,7 +269,7 @@ namespace System.Text.Json
                 }
             }
 
-            string? GetPropertyName(in ReadStackFrame frame)
+            static string? GetPropertyName(in ReadStackFrame frame)
             {
                 string? propertyName = null;
 
@@ -296,7 +281,8 @@ namespace System.Text.Json
                     utf8PropertyName = frame.JsonPropertyInfo?.JsonPropertyName;
                     if (utf8PropertyName == null)
                     {
-                        // Attempt to get the JSON property name from the property name specified in re-entry.
+                        // Attempt to get the JSON property name set manually for dictionary
+                        // keys and serializer re-entry cases where a property is specified.
                         propertyName = frame.JsonPropertyNameAsString;
                     }
                 }
index 13abc6e..dfdfd19 100644 (file)
@@ -11,23 +11,17 @@ namespace System.Text.Json
     internal struct ReadStackFrame
     {
         // Current property values.
-        public JsonPropertyInfo JsonPropertyInfo;
+        public JsonPropertyInfo? JsonPropertyInfo;
         public StackFramePropertyState PropertyState;
         public bool UseExtensionProperty;
 
         // Support JSON Path on exceptions.
         public byte[]? JsonPropertyName; // This is Utf8 since we don't want to convert to string until an exception is thown.
-        public string? JsonPropertyNameAsString; // This is used for re-entry cases.
-
-        // Support Dictionary keys.
-        public string? KeyName;
+        public string? JsonPropertyNameAsString; // This is used for dictionary keys and re-entry cases that specify a property name.
 
         // Validation state.
         public int OriginalDepth;
-        public int OriginalPropertyDepth;
-        public long OriginalPropertyBytesConsumed;
         public JsonTokenType OriginalTokenType;
-        public JsonTokenType OriginalPropertyTokenType;
 
         // Current object (POCO or IEnumerable).
         public object? ReturnValue; // The current return value used for re-entry.
@@ -35,7 +29,6 @@ namespace System.Text.Json
         public StackFrameObjectState ObjectState; // State tracking the current object.
 
         // Preserve reference.
-        public MetadataPropertyName MetadataPropertyName;
         public string? MetadataId;
 
         // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
@@ -52,21 +45,14 @@ namespace System.Text.Json
             JsonPropertyNameAsString = null;
             PropertyState = StackFramePropertyState.None;
             MetadataId = null;
-            MetadataPropertyName = MetadataPropertyName.NoMetadata;
 
             // No need to clear these since they are overwritten each time:
             //  UseExtensionProperty
-            //  OriginalPropertyDepth
-            //  OriginalPropertyBytesConsumed
-            //  OriginalPropertyTokenType
         }
 
         public void EndElement()
         {
-            OriginalPropertyDepth = 0;
-            OriginalPropertyBytesConsumed = 0;
-            OriginalPropertyTokenType = JsonTokenType.None;
-            KeyName = null;
+            JsonPropertyNameAsString = null;
             PropertyState = StackFramePropertyState.None;
         }
 
index f41f66e..c410ef0 100644 (file)
@@ -2,14 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-#if BUILDING_INBOX_LIBRARY
-using System.Collections;
+#if NETFRAMEWORK || NETCOREAPP
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using System.Reflection.Emit;
 
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
 {
     internal sealed class ReflectionEmitMemberAccessor : MemberAccessor
     {
@@ -56,12 +55,12 @@ namespace System.Text.Json
             return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
         }
 
-        public override Action<TCollection, object> CreateAddMethodDelegate<TCollection>()
+        public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
         {
             Type collectionType = typeof(TCollection);
             Type elementType = typeof(object);
 
-            // We verified this won't be null when a created the converter that calls this method.
+            // We verified this won't be null when we created the converter that calls this method.
             MethodInfo realMethod = (collectionType.GetMethod("Push") ?? collectionType.GetMethod("Enqueue"))!;
 
             var dynamicMethod = new DynamicMethod(
@@ -78,7 +77,7 @@ namespace System.Text.Json
             generator.Emit(OpCodes.Callvirt, realMethod);
             generator.Emit(OpCodes.Ret);
 
-            return (Action<TCollection, object>)dynamicMethod.CreateDelegate(typeof(Action<TCollection, object>));
+            return (Action<TCollection, object?>)dynamicMethod.CreateDelegate(typeof(Action<TCollection, object?>));
         }
 
         public override Func<IEnumerable<TElement>, TCollection> CreateImmutableEnumerableCreateRangeDelegate<TElement, TCollection>()
index 3a46fc1..f04927f 100644 (file)
@@ -2,12 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 
-namespace System.Text.Json
+namespace System.Text.Json.Serialization
 {
     internal sealed class ReflectionMemberAccessor : MemberAccessor
     {
@@ -29,7 +28,7 @@ namespace System.Text.Json
             return () => Activator.CreateInstance(type);
         }
 
-        public override Action<TCollection, object> CreateAddMethodDelegate<TCollection>()
+        public override Action<TCollection, object?> CreateAddMethodDelegate<TCollection>()
         {
             Type collectionType = typeof(TCollection);
             Type elementType = typeof(object);
@@ -37,7 +36,7 @@ namespace System.Text.Json
             // We verified this won't be null when we created the converter for the collection type.
             MethodInfo addMethod = (collectionType.GetMethod("Push") ?? collectionType.GetMethod("Enqueue"))!;
 
-            return delegate (TCollection collection, object element)
+            return delegate (TCollection collection, object? element)
             {
                 addMethod.Invoke(collection, new object[] { element! });
             };
index 08a8477..a1530ab 100644 (file)
@@ -4,6 +4,10 @@
 
 namespace System.Text.Json
 {
+    /// <summary>
+    /// The current state of an object or collection that supports continuation.
+    /// The values are typically compared with the less-than operator so the ordering is important.
+    /// </summary>
     internal enum StackFrameObjectState : byte
     {
         None = 0,
@@ -16,7 +20,7 @@ namespace System.Text.Json
         MetadataRefPropertyEndObject, // Read EndObject for $ref.
         MetadataValuesPropertyName, // Read $values property name.
         MetadataValuesPropertyStartArray, // Read StartArray for $values.
-        MetataPropertyValue, // Whether all metadata properties has been read.
+        MetadataPropertyValue, // Whether all metadata properties has been read.
 
         CreatedObject,
         ReadElements,
index 28ce1a3..0e78bcc 100644 (file)
@@ -4,14 +4,18 @@
 
 namespace System.Text.Json
 {
+    /// <summary>
+    /// The current state of a property that supports continuation.
+    /// The values are typically compared with the less-than operator so the ordering is important.
+    /// </summary>
     internal enum StackFramePropertyState : byte
     {
         None = 0,
 
-        ReadName,
-        Name,
-        ReadValue,
-        ReadValueIsEnd,
-        TryRead,
+        ReadName,   // Read the name of the property.
+        Name,   // Verify or process the name.
+        ReadValue,  // Read the value of the property.
+        ReadValueIsEnd, // Determine if we are done reading.
+        TryRead,    // Perform the actual call to the converter's TryRead().
     }
 }
index 9bd1937..4debd5e 100644 (file)
@@ -66,7 +66,7 @@ namespace System.Text.Json
         /// <summary>
         /// Initializes the state for the first type being serialized.
         /// </summary>
-        public void InitializeRoot(Type type, JsonSerializerOptions options)
+        public void InitializeRoot(Type type, JsonSerializerOptions options, bool supportContinuation)
         {
             JsonClassInfo jsonClassInfo = options.GetOrAddClass(type);
             Debug.Assert(jsonClassInfo.ClassType != ClassType.Invalid);
@@ -82,22 +82,8 @@ namespace System.Text.Json
             {
                 ReferenceResolver = new DefaultReferenceResolver(writing: true);
             }
-        }
-
-        public MetadataPropertyName GetResolvedReferenceHandling(JsonConverter converter, object value, out string? referenceId)
-        {
-            if (!converter.CanHaveMetadata)
-            {
-                referenceId = default;
-                return MetadataPropertyName.NoMetadata;
-            }
-
-            if (ReferenceResolver.TryGetOrAddReferenceOnSerialize(value, out referenceId))
-            {
-                return MetadataPropertyName.Ref;
-            }
 
-            return MetadataPropertyName.Id;
+            SupportContinuation = supportContinuation;
         }
 
         public void Push()
index fe826d0..01292e9 100644 (file)
@@ -23,10 +23,10 @@ namespace System.Text.Json
         /// For objects, it is either the policy property for the class or the current property.
         /// For collections, it is either the policy property for the class or the policy property for the current element.
         /// </remarks>
-        public JsonPropertyInfo DeclaredJsonPropertyInfo;
+        public JsonPropertyInfo? DeclaredJsonPropertyInfo;
 
         /// <summary>
-        /// Used when processing dictionaries.
+        /// Used when processing extension data dictionaries.
         /// </summary>
         public bool IgnoreDictionaryKeyPolicy;
 
@@ -36,15 +36,9 @@ namespace System.Text.Json
         public JsonClassInfo JsonClassInfo;
 
         /// <summary>
-        /// The key name for a dictionary value.
-        /// </summary>
-        public string KeyName;
-
-        /// <summary>
         /// Validation state for a class.
         /// </summary>
         public int OriginalDepth;
-        public int OriginalPropertyDepth;
 
         // Class-level state for collections.
         public bool ProcessedStartToken;
@@ -75,9 +69,8 @@ namespace System.Text.Json
         /// </remarks>
         public JsonPropertyInfo? PolymorphicJsonPropertyInfo;
 
-        public void EndElement()
+        public void EndDictionaryElement()
         {
-            OriginalPropertyDepth = 0;
             PropertyState = StackFramePropertyState.None;
         }
 
@@ -85,8 +78,6 @@ namespace System.Text.Json
         {
             DeclaredJsonPropertyInfo = null!;
             JsonPropertyNameAsString = null;
-            KeyName = null!;
-            OriginalPropertyDepth = 0;
             PolymorphicJsonPropertyInfo = null;
             PropertyState = StackFramePropertyState.None;
         }
@@ -95,10 +86,9 @@ namespace System.Text.Json
         /// Return the property that contains the correct polymorphic properties including
         /// the ClassType and ConverterBase.
         /// </summary>
-        /// <returns></returns>
         public JsonPropertyInfo GetPolymorphicJsonPropertyInfo()
         {
-            return PolymorphicJsonPropertyInfo != null ? PolymorphicJsonPropertyInfo : DeclaredJsonPropertyInfo;
+            return PolymorphicJsonPropertyInfo ?? DeclaredJsonPropertyInfo!;
         }
 
         /// <summary>
@@ -107,10 +97,6 @@ namespace System.Text.Json
         public JsonConverter InitializeReEntry(Type type, JsonSerializerOptions options, string? propertyName = null)
         {
             JsonClassInfo newClassInfo = options.GetOrAddClass(type);
-            if (newClassInfo.ClassType == ClassType.Invalid)
-            {
-                ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(type);
-            }
 
             // todo: check if type==newtype and skip below?
 
index bc2035a..475f513 100644 (file)
@@ -21,23 +21,25 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static NotSupportedException GetNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type? parentType, MemberInfo? memberInfo)
+        public static NotSupportedException GetNotSupportedException_SerializationNotSupported(Type propertyType, Type? parentType, MemberInfo? memberInfo)
         {
             if (parentType != null && parentType != typeof(object) && memberInfo != null)
             {
-                return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollection, propertyType, $"{parentType}.{memberInfo.Name}"));
+                return new NotSupportedException(SR.Format(SR.SerializationNotSupported, propertyType, $"{parentType}.{memberInfo.Name}"));
             }
 
-            return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollectionType, propertyType));
+            return new NotSupportedException(SR.Format(SR.SerializationNotSupportedType, propertyType));
         }
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static NotSupportedException ThrowNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type? parentType = null, MemberInfo? memberInfo = null)
+        public static NotSupportedException ThrowNotSupportedException_SerializationNotSupported(Type propertyType, Type? parentType = null, MemberInfo? memberInfo = null)
         {
-            throw GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
+            throw GetNotSupportedException_SerializationNotSupported(propertyType, parentType, memberInfo);
         }
 
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowInvalidOperationException_SerializerCycleDetected(int maxDepth)
         {
             throw new JsonException(SR.Format(SR.SerializerCycleDetected, maxDepth));
@@ -54,21 +56,6 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, string path, Exception? innerException = null)
-        {
-            string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType) + $" Path: {path}.";
-            throw new JsonException(message, path, null, null, innerException);
-        }
-
-        [DoesNotReturn]
-        [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_DepthTooLarge(int currentDepth, JsonSerializerOptions options)
-        {
-            throw new JsonException(SR.Format(SR.DepthTooLarge, currentDepth, options.EffectiveMaxDepth));
-        }
-
-        [DoesNotReturn]
-        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowJsonException_SerializationConverterRead(JsonConverter? converter)
         {
             var ex = new JsonException(SR.Format(SR.SerializationConverterRead, converter));
@@ -323,36 +310,22 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties()
+        public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(ReadOnlySpan<byte> propertyName, ref ReadStack state)
         {
+            state.Current.JsonPropertyName = propertyName.ToArray();
             ThrowJsonException(SR.MetadataReferenceCannotContainOtherProperties);
         }
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties_Dictionary(ref ReadStackFrame current)
-        {
-            current.KeyName = null;
-            ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties();
-        }
-
-        [DoesNotReturn]
-        [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataIdIsNotFirstProperty()
+        public static void ThrowJsonException_MetadataIdIsNotFirstProperty(ReadOnlySpan<byte> propertyName, ref ReadStack state)
         {
+            state.Current.JsonPropertyName = propertyName.ToArray();
             ThrowJsonException(SR.MetadataIdIsNotFirstProperty);
         }
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataIdIsNotFirstProperty_Dictionary(ref ReadStackFrame current)
-        {
-            current.KeyName = null;
-            ThrowJsonException_MetadataIdIsNotFirstProperty();
-        }
-
-        [DoesNotReturn]
-        [MethodImpl(MethodImplOptions.NoInlining)]
         public static void ThrowJsonException_MetadataMissingIdBeforeValues()
         {
             ThrowJsonException(SR.MetadataPreservedArrayPropertyNotFound);
@@ -365,7 +338,7 @@ namespace System.Text.Json
             // Set PropertyInfo or KeyName to write down the conflicting property name in JsonException.Path
             if (state.Current.IsProcessingDictionary())
             {
-                state.Current.KeyName = reader.GetString();
+                state.Current.JsonPropertyNameAsString = reader.GetString();
             }
             else
             {
@@ -377,8 +350,11 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataDuplicateIdFound(string id)
+        public static void ThrowJsonException_MetadataDuplicateIdFound(string id, ref ReadStack state)
         {
+            // Set so JsonPath throws exception with $id in it.
+            state.Current.JsonPropertyName = JsonSerializer.s_metadataId.EncodedUtf8Bytes.ToArray();
+
             ThrowJsonException(SR.Format(SR.MetadataDuplicateIdFound, id));
         }
 
@@ -391,7 +367,7 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_MetadataPreservedArrayInvalidProperty(Type propertyType, in Utf8JsonReader reader, ref ReadStack state)
+        public static void ThrowJsonException_MetadataPreservedArrayInvalidProperty(Type propertyType, in Utf8JsonReader reader)
         {
             string propertyName = reader.GetString()!;
 
@@ -415,5 +391,26 @@ namespace System.Text.Json
         {
             ThrowJsonException(SR.Format(SR.MetadataCannotParsePreservedObjectToImmutable, propertyType));
         }
+
+        [DoesNotReturn]
+        internal static void ThrowUnexpectedMetadataException(
+            ReadOnlySpan<byte> propertyName,
+            ref Utf8JsonReader reader,
+            ref ReadStack state)
+        {
+            MetadataPropertyName name = JsonSerializer.GetMetadataPropertyName(propertyName);
+            if (name == MetadataPropertyName.Id)
+            {
+                ThrowJsonException_MetadataIdIsNotFirstProperty(propertyName, ref state);
+            }
+            else if (name == MetadataPropertyName.Ref)
+            {
+                ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(propertyName, ref state);
+            }
+            else
+            {
+                ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader);
+            }
+        }
     }
 }
index ccbe540..45b4d2c 100644 (file)
@@ -654,13 +654,13 @@ namespace System.Text.Json.Serialization.Tests
 
         class Dealer
         {
-            private string _Networks;
+            private string _networks;
 
             [JsonIgnore]
             public string Networks
             {
-                get => _Networks;
-                set => _Networks = value ?? string.Empty;
+                get => _networks;
+                set => _networks = value ?? string.Empty;
             }
 
             public IEnumerable<string> NetworkCodeList
index 89fc783..d7b8bf8 100644 (file)
@@ -136,7 +136,8 @@ namespace System.Text.Json.Serialization.Tests
         {
             public override bool CanConvert(Type typeToConvert)
             {
-                return true;
+                // To verify the nullable converter, don't convert Nullable.
+                return Nullable.GetUnderlyingType(typeToConvert) == null;
             }
 
             public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
@@ -157,6 +158,10 @@ namespace System.Text.Json.Serialization.Tests
 
             ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<int>("0", options));
             Assert.Contains(typeof(int).ToString(), ex.Message);
+
+            // This will invoke the Nullable converter which should detect a null converter.
+            ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<int?>("0", options));
+            Assert.Contains(typeof(int).ToString(), ex.Message);
         }
 
         private class Level1
index bec95b2..5e061ce 100644 (file)
@@ -2033,7 +2033,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(Json, json);
         }
 
-        public class DictionaryThatHasIncomatibleEnumerator : IDictionary
+        public class DictionaryThatHasIncompatibleEnumerator : IDictionary
         {
             Hashtable _inner = new Hashtable();
 
@@ -2117,8 +2117,8 @@ namespace System.Text.Json.Serialization.Tests
         {
             const string Json = @"{""One"":1,""Two"":2}";
 
-            DictionaryThatHasIncomatibleEnumerator dictionary;
-            dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncomatibleEnumerator>(Json);
+            DictionaryThatHasIncompatibleEnumerator dictionary;
+            dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncompatibleEnumerator>(Json);
             Assert.Equal(1, ((JsonElement)dictionary["One"]).GetInt32());
             Assert.Equal(2, ((JsonElement)dictionary["Two"]).GetInt32());
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(dictionary));
@@ -2129,13 +2129,22 @@ namespace System.Text.Json.Serialization.Tests
         {
             const string Json = @"{""One"":{""Id"":1},""Two"":{""Id"":2}}";
 
-            DictionaryThatHasIncomatibleEnumerator dictionary;
-            dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncomatibleEnumerator>(Json);
+            DictionaryThatHasIncompatibleEnumerator dictionary;
+            dictionary = JsonSerializer.Deserialize<DictionaryThatHasIncompatibleEnumerator>(Json);
             Assert.Equal(1, ((JsonElement)dictionary["One"]).GetProperty("Id").GetInt32());
             Assert.Equal(2, ((JsonElement)dictionary["Two"]).GetProperty("Id").GetInt32());
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(dictionary));
         }
 
+
+        [Fact]
+        public static void VerifyIDictionaryWithNonStringKey()
+        {
+            IDictionary dictionary = new Hashtable();
+            dictionary.Add(1, "value");
+            Assert.Throws<JsonException>(() => JsonSerializer.Serialize(dictionary));
+        }
+
         public class ClassWithoutParameterlessCtor
         {
             public ClassWithoutParameterlessCtor(int num) { }
index f876a34..d374f7c 100644 (file)
@@ -429,5 +429,33 @@ namespace System.Text.Json.Serialization.Tests
             public Dictionary<string, ChildClass> MyDictionary { get; set; }
             public ChildClass[] Children { get; set; }
         }
+
+        private class ClassWithInvalidArray
+        {
+            public int[,] UnsupportedArray { get; set; }
+        }
+
+        private class ClassWithInvalidDictionary
+        {
+            public Dictionary<string, int[,]> UnsupportedDictionary { get; set; }
+        }
+
+        [Fact]
+        public static void ClassWithUnsupportedCollectionTypes()
+        {
+            Exception ex;
+
+            ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithInvalidArray>(@"{""UnsupportedArray"":[]}"));
+
+            // The exception should contain the parent type and the property name.
+            Assert.Contains("ClassWithInvalidArray.UnsupportedArray", ex.ToString());
+
+            ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithInvalidDictionary>(@"{""UnsupportedDictionary"":{}}"));
+            Assert.Contains("System.Int32[,]", ex.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("ClassWithInvalidDictionary.UnsupportedDictionary", ex.ToString());
+        }
     }
 }
index 6d070a6..b23ef61 100644 (file)
@@ -2,7 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using Xunit;
 
@@ -688,5 +690,70 @@ namespace System.Text.Json.Serialization.Tests
 
             public Dictionary<string, object> ActualDictionary { get; set; }
         }
+
+        [Fact]
+        public static void CustomObjectConverterInExtensionProperty()
+        {
+            const string Json = "{\"hello\": \"world\"}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonObjectConverter());
+
+            ClassWithExtensionPropertyAsObject obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsObject>(Json, options);
+            object overflowProp = obj.MyOverflow["hello"];
+            Assert.IsType<string>(overflowProp);
+            Assert.Equal("world!!!", ((string)overflowProp));
+
+            string newJson = JsonSerializer.Serialize(obj, options);
+            Assert.Equal("{\"hello\":\"world!!!\"}", newJson);
+        }
+
+        private class JsonObjectConverter : JsonConverter<object>
+        {
+            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                return reader.GetString() + "!!!";
+            }
+
+            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
+            {
+                // Since we are converter for object, the string converter will be called instead of this.
+                throw new InvalidOperationException();
+            }
+        }
+
+        [Fact]
+        public static void CustomJsonElementConverterInExtensionProperty()
+        {
+            const string Json = "{\"hello\": \"world\"}";
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonElementConverter());
+
+            ClassWithExtensionPropertyAsJsonElement obj = JsonSerializer.Deserialize<ClassWithExtensionPropertyAsJsonElement>(Json, options);
+            JsonElement overflowProp = obj.MyOverflow["hello"];
+            Assert.Equal(JsonValueKind.Undefined, overflowProp.ValueKind);
+
+            string newJson = JsonSerializer.Serialize(obj, options);
+            Assert.Equal("{\"hello\":{\"Hi\":\"There\"}}", newJson);
+        }
+
+        private class JsonElementConverter : JsonConverter<JsonElement>
+        {
+            public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                // Just return an empty JsonElement.
+                reader.Skip();
+                return new JsonElement();
+            }
+
+            public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
+            {
+                // Write a string we can test against easily.
+                writer.WriteStartObject();
+                writer.WriteString("Hi", "There");
+                writer.WriteEndObject();
+            }
+        }
     }
 }
index 3b81aaa..a11af83 100644 (file)
@@ -22,6 +22,14 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         #region Root Object
+        [Fact]
+        public static void ObjectWithoutMetadata()
+        {
+            string json = "{}";
+            Employee employee = JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve);
+            Assert.NotNull(employee);
+        }
+
         [Fact] //Employee list as a property and then use reference to itself on nested Employee.
         public static void ObjectReferenceLoop()
         {
@@ -250,6 +258,14 @@ namespace System.Text.Json.Serialization.Tests
         #endregion
 
         #region Root Dictionary
+        [Fact]
+        public static void DictionaryWithoutMetadata()
+        {
+            string json = "{}";
+            Dictionary<string, string> dictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(json, s_deserializerOptionsPreserve);
+            Assert.NotNull(dictionary);
+        }
+
         [Fact] //Employee list as a property and then use reference to itself on nested Employee.
         public static void DictionaryReferenceLoop()
         {
@@ -891,7 +907,7 @@ namespace System.Text.Json.Serialization.Tests
             ]";
 
             JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<EmployeeStruct>>(json, s_deserializerOptionsPreserve));
-            Assert.Equal("$[0].$id", ex.Path);
+            Assert.Equal("$[1].$ref", ex.Path);
             Assert.Contains($"'{typeof(EmployeeStruct)}'", ex.Message);
         }
         #endregion
@@ -1122,7 +1138,7 @@ namespace System.Text.Json.Serialization.Tests
             }";
 
             ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
-            Assert.Equal("$.Manager.$ref", ex.Path);
+            Assert.Equal("$.Manager.Name", ex.Path);
 
             //Metadata property before $ref
             json = @"{
@@ -1148,7 +1164,7 @@ namespace System.Text.Json.Serialization.Tests
             }";
 
             ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
-            Assert.Equal("$.Manager.$ref", ex.Path);
+            Assert.Equal("$.Manager.$id", ex.Path);
         }
 
         [Fact]
@@ -1168,6 +1184,25 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Contains("'1'", ex.Message);
             Assert.Equal("$[0].$ref", ex.Path);
         }
+
+        [Theory]
+        [MemberData(nameof(ReadSuccessCases))]
+        public static void ReadTestClassesWithExtensionOption(Type classType, byte[] data)
+        {
+            var options = new JsonSerializerOptions { ReferenceHandling = ReferenceHandling.Preserve };
+            object obj = JsonSerializer.Deserialize(data, classType, options);
+            Assert.IsAssignableFrom<ITestClass>(obj);
+            ((ITestClass)obj).Verify();
+        }
+
+        public static IEnumerable<object[]> ReadSuccessCases
+        {
+            get
+            {
+                return TestData.ReadSuccessCases;
+            }
+        }
+
         #endregion
 
         #region Preserved objects ($id)
@@ -1289,7 +1324,7 @@ namespace System.Text.Json.Serialization.Tests
 
             JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<int>>(json, s_deserializerOptionsPreserve));
 
-            Assert.Equal("$.$values", ex.Path); 
+            Assert.Equal("$.$values", ex.Path);
         }
 
         [Fact]
@@ -1329,7 +1364,7 @@ namespace System.Text.Json.Serialization.Tests
 
             JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<Employee>>(json, s_deserializerOptionsPreserve));
 
-            Assert.Equal("$", ex.Path);
+            Assert.Equal("$.LeadingProperty", ex.Path);
             Assert.Contains(typeof(List<Employee>).ToString(), ex.Message);
 
             json = @"{
@@ -1340,7 +1375,7 @@ namespace System.Text.Json.Serialization.Tests
 
             ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<Employee>>(json, s_deserializerOptionsPreserve));
 
-            Assert.Equal("$", ex.Path);
+            Assert.Equal("$.TrailingProperty", ex.Path);
             Assert.Contains(typeof(List<Employee>).ToString(), ex.Message);
             Assert.Contains("TrailingProperty", ex.Message);
         }
index e19db69..409b897 100644 (file)
@@ -314,10 +314,12 @@ namespace System.Text.Json.Serialization.Tests
         [InlineData(2, false, false)]
         [InlineData(4, false, false)]
         [InlineData(8, false, false)]
-        [InlineData(16, false, false)]
+        [InlineData(16, false, false)] // This results a reader\writer depth of 324 which currently works on all test platforms.
         public static async Task DeepNestedJsonFileTest(int depthFactor, bool ignoreNull, bool writeIndented)
         {
-            int length = 10 * depthFactor;
+            const int ListLength = 10;
+
+            int length = ListLength * depthFactor;
             List<Order>[] orders = new List<Order>[length];
             orders[0] = PopulateLargeObject(1);
             for (int i = 1; i < length; i++ )
@@ -328,7 +330,7 @@ namespace System.Text.Json.Serialization.Tests
 
             JsonSerializerOptions options = new JsonSerializerOptions()
             {
-                MaxDepth = depthFactor * 64,
+                MaxDepth = (ListLength * depthFactor * 2) + 4, // Order-to-RelatedOrder has a depth of 2.
                 IgnoreNullValues = ignoreNull,
                 WriteIndented = writeIndented
             };
@@ -356,10 +358,12 @@ namespace System.Text.Json.Serialization.Tests
 
         [Theory]
         [InlineData(1)]
-        [InlineData(16)]
-        public static async Task DeepNestedJsonFileCircularDependencyTest(int depthFactor)
+        [InlineData(4)]
+        public static async Task NestedJsonFileCircularDependencyTest(int depthFactor)
         {
-            int length = 10 * depthFactor;
+            const int ListLength = 2;
+
+            int length = ListLength * depthFactor;
             List<Order>[] orders = new List<Order>[length];
             orders[0] = PopulateLargeObject(1000);
             for (int i = 1; i < length; i++)
@@ -367,14 +371,18 @@ namespace System.Text.Json.Serialization.Tests
                 orders[i] = PopulateLargeObject(1);
                 orders[i - 1][0].RelatedOrder = orders[i];
             }
-            orders[length - 1][0].RelatedOrder = orders[0];
 
             JsonSerializerOptions options = new JsonSerializerOptions()
             {
-                MaxDepth = depthFactor * 64,
                 IgnoreNullValues = true
             };
 
+            // Ensure no exception for default settings (MaxDepth=64) and no cycle.
+            JsonSerializer.Serialize(orders[0], options);
+
+            // Create a cycle.
+            orders[length - 1][0].RelatedOrder = orders[0];
+
             Assert.Throws<JsonException> (() => JsonSerializer.Serialize(orders[0], options));
 
             using (var memoryStream = new MemoryStream())
index bb98203..ed649d3 100644 (file)
@@ -18,12 +18,14 @@ namespace System.Text.Json.Serialization.Tests
 
             ConcurrentQueue<string> qc = JsonSerializer.Deserialize<ConcurrentQueue<string>>(@"[""1""]");
             Assert.Equal(1, qc.Count);
-            qc.TryPeek(out string val);
+            bool found = qc.TryPeek(out string val);
+            Assert.True(found);
             Assert.Equal("1", val);
 
             ConcurrentStack<string> qs = JsonSerializer.Deserialize<ConcurrentStack<string>>(@"[""1""]");
             Assert.Equal(1, qs.Count);
-            qs.TryPeek(out val);
+            found = qs.TryPeek(out val);
+            Assert.True(found);
             Assert.Equal("1", val);
         }
 
index dc18020..57c6902 100644 (file)
@@ -32,15 +32,15 @@ namespace System.Text.Json.Serialization.Tests
         [Fact]
         public static void Read_SpecializedCollection_Throws()
         {
-            //// Add method for this collection only accepts strings, even though it only implements IList which usually
-            //// indicates that the element type is typeof(object).
-            //Assert.Throws<InvalidCastException>(() => JsonSerializer.Deserialize<StringCollection>(@"[""1"", ""2""]"));
+            // Add method for this collection only accepts strings, even though it only implements IList which usually
+            // indicates that the element type is typeof(object).
+            Assert.Throws<InvalidCastException>(() => JsonSerializer.Deserialize<StringCollection>(@"[""1"", ""2""]"));
 
-            //// Not supported. Not IList, and we don't detect the add method for this collection.
-            //Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StringDictionary>(@"[{""Key"": ""key"",""Value"":""value""}]"));
+            // Not supported. Not IList, and we don't detect the add method for this collection.
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StringDictionary>(@"[{""Key"": ""key"",""Value"":""value""}]"));
 
-            //// Int key is not allowed.
-            //Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<HybridDictionary>(@"{1:""value""}"));
+            // Int key is not allowed.
+            Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<HybridDictionary>(@"{1:""value""}"));
 
             // Runtime type in this case is IOrderedDictionary (we don't replace with concrete type), which we can't instantiate.
             Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<IOrderedDictionary>(@"{""first"":""John"",""second"":""Jane"",""third"":""Jet""}"));