diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index f1d11f8a343c21..226f99b2b3927c 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -354,11 +354,11 @@
The type '{0}' cannot have more than one property that has the attribute '{1}'.
-
- The collection type '{0}' is not supported.
+
+ The type '{0}' is not supported.
-
- The collection type '{0}' on '{1}' is not supported.
+
+ The type '{0}' on '{1}' is not supported.
'{0}' is invalid after '/' at the beginning of the comment. Expected either '/' or '*'.
@@ -485,4 +485,4 @@
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.
-
+
\ No newline at end of file
diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index 43c1fa300ec804..7e7ba368e04b28 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -51,11 +51,35 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -64,7 +88,6 @@
-
@@ -72,6 +95,8 @@
+
+
@@ -80,48 +105,37 @@
-
-
-
+
+
-
-
+
-
-
-
-
+
-
-
+
-
-
-
-
-
-
+
@@ -129,6 +143,7 @@
+
@@ -139,6 +154,8 @@
+
+
@@ -232,4 +249,4 @@
-
\ No newline at end of file
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
index e9fbe8fc2b9db7..ce687afa263edb 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
@@ -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
{
+ ///
+ /// Returns the span for the given reader.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan GetSpan(this ref Utf8JsonReader reader)
+ {
+ return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ }
+
#if !BUILDING_INBOX_LIBRARY
///
/// Returns if is a valid Unicode scalar
@@ -64,6 +75,17 @@ public static bool IsInRangeInclusive(JsonTokenType value, JsonTokenType lowerBo
///
public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';
+ ///
+ /// 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.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadWithVerify(this ref Utf8JsonReader reader)
+ {
+ bool result = reader.Read();
+ Debug.Assert(result);
+ }
+
///
/// Calls Encoding.UTF8.GetString that supports netstandard.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
index e4dad76cb6acf6..69448f1ba83336 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
@@ -13,15 +13,17 @@ namespace System.Text.Json
///
internal enum ClassType : byte
{
- // typeof(object)
- Unknown = 0x1,
- // POCO or rich data type
- Object = 0x2,
- // Value or object with a converter.
- Value = 0x4,
- // IEnumerable
+ // JsonObjectConverter<> - objects with properties.
+ Object = 0x1,
+ // JsonConverter<> - simple values.
+ Value = 0x2,
+ // JsonValueConverter<> - simple values that need to re-enter the serializer such as KeyValuePair.
+ NewValue = 0x4,
+ // JsonIEnumerbleConverter<> - all enumerable collections except dictionaries.
Enumerable = 0x8,
- // IDictionary
+ // JsonDictionaryConverter<,> - dictionary types.
Dictionary = 0x10,
+ // Invalid (not used directly for serialization)
+ Invalid = 0x20
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs
deleted file mode 100644
index a7e4602c1fb9af..00000000000000
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultArrayConverter : JsonEnumerableConverter
- {
- public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
- {
- Type elementType = state.Current.GetElementType();
-
- Array array;
-
- if (sourceList.Count > 0 && sourceList[0] is Array probe)
- {
- array = Array.CreateInstance(probe.GetType(), sourceList.Count);
-
- int i = 0;
- foreach (IList? child in sourceList)
- {
- if (child is Array childArray)
- {
- array.SetValue(childArray, i++);
- }
- }
- }
- else
- {
- array = Array.CreateInstance(elementType, sourceList.Count);
- sourceList.CopyTo(array, 0);
- }
-
- return array;
- }
- }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs
deleted file mode 100644
index 5f4635bc905082..00000000000000
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-using System.Collections.Generic;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal class JsonEnumerableT : ICollection, IEnumerable, IList, IReadOnlyCollection, IReadOnlyList
- {
- List _list;
-
- public JsonEnumerableT(IList sourceList)
- {
- // TODO: Change sourceList from IList to List so we can do a direct assignment here.
- _list = new List();
-
- foreach (object item in sourceList)
- {
- _list.Add((T)item);
- }
- }
-
- public T this[int index] { get => (T)_list[index]; set => _list[index] = value; }
-
- public int Count => _list.Count;
-
- public bool IsReadOnly => false;
-
- public void Add(T item)
- {
- _list.Add(item);
- }
-
- public void Clear()
- {
- _list.Clear();
- }
-
- public bool Contains(T item)
- {
- return _list.Contains(item);
- }
-
- public void CopyTo(T[] array, int arrayIndex)
- {
- _list.CopyTo(array, arrayIndex);
- }
-
- public IEnumerator GetEnumerator()
- {
- return _list.GetEnumerator();
- }
-
- public int IndexOf(T item)
- {
- return _list.IndexOf(item);
- }
-
- public void Insert(int index, T item)
- {
- _list.Insert(index, item);
- }
-
- public bool Remove(T item)
- {
- return _list.Remove(item);
- }
-
- public void RemoveAt(int index)
- {
- _list.RemoveAt(index);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
-
- internal sealed class DefaultEnumerableConverter : JsonEnumerableConverter
- {
- public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
- {
- Type elementType = state.Current.GetElementType();
-
- Type t = typeof(JsonEnumerableT<>).MakeGenericType(elementType);
- return (IEnumerable)Activator.CreateInstance(t, sourceList);
- }
- }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs
deleted file mode 100644
index e19a5f51d75473..00000000000000
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-using System.Diagnostics;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultImmutableDictionaryConverter : JsonDictionaryConverter
- {
- public const string ImmutableDictionaryTypeName = "System.Collections.Immutable.ImmutableDictionary";
- public const string ImmutableDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableDictionary`2";
- public const string ImmutableDictionaryGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableDictionary`2";
-
- public const string ImmutableSortedDictionaryTypeName = "System.Collections.Immutable.ImmutableSortedDictionary";
- public const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2";
-
- public static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
- {
- // Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
- string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);
-
- // Exit if we have registered this immutable dictionary type.
- if (options.CreateRangeDelegatesContainsKey(delegateKey))
- {
- return;
- }
-
- // Get the constructing type.
- Type constructingType = underlyingType.Assembly.GetType(constructingTypeName)!;
-
- // Create a delegate which will point to the CreateRange method.
- ImmutableCollectionCreator createRangeDelegate = options.MemberAccessorStrategy.ImmutableDictionaryCreateRange(constructingType, immutableCollectionType, elementType);
-
- // Cache the delegate
- options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
- }
-
- public static bool IsImmutableDictionary(Type type)
- {
- if (!type.IsGenericType)
- {
- return false;
- }
-
- switch (type.GetGenericTypeDefinition().FullName)
- {
- case ImmutableDictionaryGenericTypeName:
- case ImmutableDictionaryGenericInterfaceTypeName:
- case ImmutableSortedDictionaryGenericTypeName:
- return true;
- default:
- return false;
- }
- }
-
- public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
- {
- Type immutableCollectionType = state.Current.JsonPropertyInfo!.RuntimePropertyType;
-
- JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo!;
- Type elementType = elementClassInfo.Type;
-
- string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _);
-
- JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootProperty(options);
- Debug.Assert(propertyInfo != null);
- return propertyInfo.CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, sourceDictionary, options);
- }
- }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs
deleted file mode 100644
index b347bb59ea1bdf..00000000000000
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-using System.Diagnostics;
-
-namespace System.Text.Json.Serialization.Converters
-{
- // This converter returns enumerables in the System.Collections.Immutable namespace.
- internal sealed class DefaultImmutableEnumerableConverter : JsonEnumerableConverter
- {
- public const string ImmutableArrayTypeName = "System.Collections.Immutable.ImmutableArray";
- public const string ImmutableArrayGenericTypeName = "System.Collections.Immutable.ImmutableArray`1";
-
- private const string ImmutableListTypeName = "System.Collections.Immutable.ImmutableList";
- public const string ImmutableListGenericTypeName = "System.Collections.Immutable.ImmutableList`1";
- public const string ImmutableListGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableList`1";
-
- private const string ImmutableStackTypeName = "System.Collections.Immutable.ImmutableStack";
- public const string ImmutableStackGenericTypeName = "System.Collections.Immutable.ImmutableStack`1";
- public const string ImmutableStackGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableStack`1";
-
- private const string ImmutableQueueTypeName = "System.Collections.Immutable.ImmutableQueue";
- public const string ImmutableQueueGenericTypeName = "System.Collections.Immutable.ImmutableQueue`1";
- public const string ImmutableQueueGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableQueue`1";
-
- public const string ImmutableSortedSetTypeName = "System.Collections.Immutable.ImmutableSortedSet";
- public const string ImmutableSortedSetGenericTypeName = "System.Collections.Immutable.ImmutableSortedSet`1";
-
- private const string ImmutableHashSetTypeName = "System.Collections.Immutable.ImmutableHashSet";
- public const string ImmutableHashSetGenericTypeName = "System.Collections.Immutable.ImmutableHashSet`1";
- public const string ImmutableSetGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableSet`1";
-
- public static string GetDelegateKey(
- Type immutableCollectionType,
- Type elementType,
- out Type underlyingType,
- out string constructingTypeName)
- {
- // Use the generic type definition of the immutable collection to determine an appropriate constructing type,
- // i.e. a type that we can invoke the `CreateRange` method on, which returns an assignable immutable collection.
- underlyingType = immutableCollectionType.GetGenericTypeDefinition();
-
- switch (underlyingType.FullName)
- {
- case ImmutableArrayGenericTypeName:
- constructingTypeName = ImmutableArrayTypeName;
- break;
- case ImmutableListGenericTypeName:
- case ImmutableListGenericInterfaceTypeName:
- constructingTypeName = ImmutableListTypeName;
- break;
- case ImmutableStackGenericTypeName:
- case ImmutableStackGenericInterfaceTypeName:
- constructingTypeName = ImmutableStackTypeName;
- break;
- case ImmutableQueueGenericTypeName:
- case ImmutableQueueGenericInterfaceTypeName:
- constructingTypeName = ImmutableQueueTypeName;
- break;
- case ImmutableSortedSetGenericTypeName:
- constructingTypeName = ImmutableSortedSetTypeName;
- break;
- case ImmutableHashSetGenericTypeName:
- case ImmutableSetGenericInterfaceTypeName:
- constructingTypeName = ImmutableHashSetTypeName;
- break;
- case DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericTypeName:
- case DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericInterfaceTypeName:
- constructingTypeName = DefaultImmutableDictionaryConverter.ImmutableDictionaryTypeName;
- break;
- case DefaultImmutableDictionaryConverter.ImmutableSortedDictionaryGenericTypeName:
- constructingTypeName = DefaultImmutableDictionaryConverter.ImmutableSortedDictionaryTypeName;
- break;
- default:
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(immutableCollectionType, null, null);
- }
-
- return $"{constructingTypeName}:{elementType.FullName}";
- }
-
- public static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options)
- {
- // Get a unique identifier for a delegate which will point to the appropiate CreateRange method.
- string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName);
-
- // Exit if we have registered this immutable collection type.
- if (options.CreateRangeDelegatesContainsKey(delegateKey))
- {
- return;
- }
-
- // Get the constructing type.
- Type constructingType = underlyingType.Assembly.GetType(constructingTypeName)!;
-
- // Create a delegate which will point to the CreateRange method.
- ImmutableCollectionCreator createRangeDelegate = options.MemberAccessorStrategy.ImmutableCollectionCreateRange(constructingType, immutableCollectionType, elementType);
-
- // Cache the delegate
- options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
- }
-
- public static bool IsImmutableEnumerable(Type type)
- {
- if (!type.IsGenericType)
- {
- return false;
- }
-
- switch (type.GetGenericTypeDefinition().FullName)
- {
- case ImmutableArrayGenericTypeName:
- case ImmutableListGenericTypeName:
- case ImmutableListGenericInterfaceTypeName:
- case ImmutableStackGenericTypeName:
- case ImmutableStackGenericInterfaceTypeName:
- case ImmutableQueueGenericTypeName:
- case ImmutableQueueGenericInterfaceTypeName:
- case ImmutableSortedSetGenericTypeName:
- case ImmutableHashSetGenericTypeName:
- case ImmutableSetGenericInterfaceTypeName:
- return true;
- default:
- return false;
- }
- }
-
- public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
- {
- Type immutableCollectionType = state.Current.JsonPropertyInfo!.RuntimePropertyType;
-
- JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo!;
- Type elementType = elementClassInfo.Type;
-
- string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
-
- JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootProperty(options);
- Debug.Assert(propertyInfo != null);
- return propertyInfo.CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, sourceList, options);
- }
- }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonArrayConverter.cs
new file mode 100644
index 00000000000000..4d3e2fc2cd9e98
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonArrayConverter.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ ///
+ /// Converter for System.Array.
+ ///
+ internal sealed class JsonArrayConverter
+ : JsonIEnumerableDefaultConverter
+ where TCollection: IEnumerable
+ {
+ internal override bool CanHaveIdMetadata => false;
+
+ protected override void Add(TElement value, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is List);
+ ((List)state.Current.ReturnValue!).Add(value);
+ }
+
+ protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
+ {
+ state.Current.ReturnValue = new List();
+ }
+
+ protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options)
+ {
+ Debug.Assert(state.Current.ReturnValue is List);
+ List list = (List)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;
+
+ JsonConverter elementConverter = GetElementConverter(ref state);
+ if (elementConverter.CanUseDirectReadOrWrite)
+ {
+ // Fast path that avoids validation and extra indirection.
+ for (; index < array.Length; index++)
+ {
+ elementConverter.Write(writer, array[index], options);
+ }
+ }
+ else
+ {
+ for (; index < array.Length; index++)
+ {
+ TElement element = array[index];
+ if (!elementConverter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.EnumeratorIndex = index;
+ return false;
+ }
+
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.EnumeratorIndex = ++index;
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentQueueOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentQueueOfTConverter.cs
new file mode 100644
index 00000000000000..4a4a10e8e2e369
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentQueueOfTConverter.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ internal sealed class JsonConcurrentQueueOfTConverter : JsonIEnumerableDefaultConverter
+ where TCollection : ConcurrentQueue
+ {
+ protected override void Add(TElement value, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is TCollection);
+ ((TCollection)state.Current.ReturnValue!).Enqueue(value);
+ }
+
+ protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
+ {
+ if (state.Current.JsonClassInfo.CreateObject == null)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
+ }
+
+ state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
+ }
+
+ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ IEnumerator enumerator;
+ if (state.Current.CollectionEnumerator == null)
+ {
+ enumerator = value.GetEnumerator();
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator);
+ enumerator = (IEnumerator)state.Current.CollectionEnumerator;
+ }
+
+ JsonConverter converter = GetElementConverter(ref state);
+ do
+ {
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
+ TElement element = enumerator.Current;
+ if (!converter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+ } while (enumerator.MoveNext());
+
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentStackOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentStackOfTConverter.cs
new file mode 100644
index 00000000000000..5321d8d7161460
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConcurrentStackOfTConverter.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ internal sealed class JsonConcurrentStackOfTConverter : JsonIEnumerableDefaultConverter
+ where TCollection : ConcurrentStack
+ {
+ protected override void Add(TElement value, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is TCollection);
+ ((TCollection)state.Current.ReturnValue!).Push(value);
+ }
+
+ protected override void CreateCollection(ref ReadStack state, JsonSerializerOptions options)
+ {
+ if (state.Current.JsonClassInfo.CreateObject == null)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
+ }
+
+ state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
+ }
+
+ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ IEnumerator enumerator;
+ if (state.Current.CollectionEnumerator == null)
+ {
+ enumerator = value.GetEnumerator();
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator);
+ enumerator = (IEnumerator)state.Current.CollectionEnumerator;
+ }
+
+ JsonConverter converter = GetElementConverter(ref state);
+ do
+ {
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
+ TElement element = enumerator.Current;
+ if (!converter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+ } while (enumerator.MoveNext());
+
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryDefaultConverter.cs
new file mode 100644
index 00000000000000..62f64bfc8be1ad
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryDefaultConverter.cs
@@ -0,0 +1,307 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ ///
+ /// Default base class implementation of JsonDictionaryConverter{TCollection} .
+ ///
+ internal abstract class JsonDictionaryDefaultConverter
+ : JsonDictionaryConverter
+ {
+ ///
+ /// When overridden, adds the value to the collection.
+ ///
+ protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
+
+ ///
+ /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
+ /// This is used with immutable collections.
+ ///
+ protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
+
+ ///
+ /// When overridden, create the collection. It may be a temporary collection or the final collection.
+ ///
+ protected virtual void CreateCollection(ref ReadStack state) { }
+
+ internal override Type ElementType => typeof(TValue);
+
+ protected static JsonConverter GetElementConverter(ref ReadStack state)
+ {
+ JsonConverter converter = (JsonConverter)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;
+ }
+
+ protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOptions options)
+ {
+ if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy)
+ {
+ key = options.DictionaryKeyPolicy.ConvertName(key);
+
+ if (key == null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
+ }
+ }
+
+ return key;
+ }
+
+ protected static JsonConverter GetValueConverter(ref WriteStack state)
+ {
+ JsonConverter converter = (JsonConverter)state.Current.DeclaredJsonPropertyInfo!.ConverterBase;
+ Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
+
+ return converter;
+ }
+
+ internal sealed override bool OnTryRead(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options,
+ ref ReadStack state,
+ out TCollection value)
+ {
+ bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();
+
+ if (!state.SupportContinuation && !shouldReadPreservedReferences)
+ {
+ // Fast path that avoids maintaining state variables and dealing with preserved references.
+
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+ }
+
+ CreateCollection(ref state);
+
+ JsonConverter elementConverter = GetElementConverter(ref state);
+ if (elementConverter.CanUseDirectReadOrWrite)
+ {
+ // Process all elements.
+ while (true)
+ {
+ // Read the key name.
+ reader.ReadWithVerify();
+
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+ }
+
+ state.Current.JsonPropertyNameAsString = reader.GetString();
+
+ // Read the value and add.
+ 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.ReadWithVerify();
+
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+ }
+
+ state.Current.JsonPropertyNameAsString = reader.GetString();
+
+ 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);
+ }
+ }
+ }
+ else
+ {
+ // Slower path that supports continuation and preserved references.
+
+ if (state.Current.ObjectState == StackFrameObjectState.None)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+ }
+
+ state.Current.ObjectState = StackFrameObjectState.StartToken;
+ }
+
+ // Handle the metadata properties.
+ if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.MetadataPropertyValue)
+ {
+ 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)
+ {
+ Debug.Assert(CanHaveIdMetadata);
+
+ value = (TCollection)state.Current.ReturnValue!;
+ if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
+ {
+ ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
+ }
+ }
+
+ state.Current.ObjectState = StackFrameObjectState.CreatedObject;
+ }
+
+ // Process all elements.
+ JsonConverter elementConverter = GetElementConverter(ref state);
+ while (true)
+ {
+ if (state.Current.PropertyState == StackFramePropertyState.None)
+ {
+ state.Current.PropertyState = StackFramePropertyState.ReadName;
+
+ // Read the key name.
+ if (!reader.Read())
+ {
+ value = default!;
+ return false;
+ }
+ }
+
+ // Determine the property.
+ if (state.Current.PropertyState < StackFramePropertyState.Name)
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+ }
+
+ state.Current.PropertyState = StackFramePropertyState.Name;
+
+ // Verify property doesn't contain metadata.
+ if (shouldReadPreservedReferences)
+ {
+ ReadOnlySpan propertyName = reader.GetSpan();
+ if (propertyName.Length > 0 && propertyName[0] == '$')
+ {
+ ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
+ }
+ }
+
+ state.Current.JsonPropertyNameAsString = reader.GetString();
+ }
+
+ if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
+ {
+ state.Current.PropertyState = StackFramePropertyState.ReadValue;
+
+ if (!SingleValueReadWithReadAhead(elementConverter.ClassType, ref reader, ref state))
+ {
+ value = default!;
+ return false;
+ }
+ }
+
+ if (state.Current.PropertyState < StackFramePropertyState.TryRead)
+ {
+ // 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)
+ {
+ value = default!;
+ return false;
+ }
+
+ Add(element, options, ref state);
+ state.Current.EndElement();
+ }
+ }
+ }
+
+ ConvertCollection(ref state, options);
+ value = (TCollection)state.Current.ReturnValue!;
+ return true;
+ }
+
+ internal sealed override bool OnTryWrite(
+ Utf8JsonWriter writer,
+ TCollection dictionary,
+ JsonSerializerOptions options,
+ ref WriteStack state)
+ {
+ if (dictionary == null)
+ {
+ writer.WriteNullValue();
+ return true;
+ }
+
+ if (!state.Current.ProcessedStartToken)
+ {
+ state.Current.ProcessedStartToken = true;
+ writer.WriteStartObject();
+
+ if (options.ReferenceHandling.ShouldWritePreservedReferences())
+ {
+ if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
+ {
+ return true;
+ }
+ }
+
+ state.Current.DeclaredJsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PolicyProperty!;
+ }
+
+ bool success = OnWriteResume(writer, dictionary, options, ref state);
+ if (success)
+ {
+ if (!state.Current.ProcessedEndToken)
+ {
+ state.Current.ProcessedEndToken = true;
+ writer.WriteEndObject();
+ }
+ }
+
+ return success;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryOfStringTValueConverter.cs
new file mode 100644
index 00000000000000..ef539d201b9d6a
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonDictionaryOfStringTValueConverter.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ ///
+ /// Converter for Dictionary{string, TValue} that (de)serializes as a JSON object with properties
+ /// representing the dictionary element key and value.
+ ///
+ internal sealed class JsonDictionaryOfStringTValueConverter
+ : JsonDictionaryDefaultConverter
+ where TCollection : Dictionary
+ {
+ protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is TCollection);
+
+ string key = state.Current.JsonPropertyNameAsString!;
+ ((TCollection)state.Current.ReturnValue!)[key] = value;
+ }
+
+ protected override void CreateCollection(ref ReadStack state)
+ {
+ if (state.Current.JsonClassInfo.CreateObject == null)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(state.Current.JsonClassInfo.Type);
+ }
+
+ state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject();
+ }
+
+ protected internal override bool OnWriteResume(
+ Utf8JsonWriter writer,
+ TCollection value,
+ JsonSerializerOptions options,
+ ref WriteStack state)
+ {
+ Dictionary.Enumerator enumerator;
+ if (state.Current.CollectionEnumerator == null)
+ {
+ enumerator = value.GetEnumerator();
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ Debug.Assert(state.Current.CollectionEnumerator is Dictionary.Enumerator);
+ enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator;
+ }
+
+ JsonConverter converter = GetValueConverter(ref state);
+ if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite)
+ {
+ // Fast path that avoids validation and extra indirection.
+ do
+ {
+ string key = GetKeyName(enumerator.Current.Key, ref state, options);
+ writer.WritePropertyName(key);
+ converter.Write(writer, enumerator.Current.Value, options);
+ } while (enumerator.MoveNext());
+ }
+ else
+ {
+ do
+ {
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
+ TValue element = enumerator.Current.Value;
+ if (state.Current.PropertyState < StackFramePropertyState.Name)
+ {
+ state.Current.PropertyState = StackFramePropertyState.Name;
+ string key = GetKeyName(enumerator.Current.Key, ref state, options);
+ writer.WritePropertyName(key);
+ }
+
+ if (!converter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
+ state.Current.EndDictionaryElement();
+ } while (enumerator.MoveNext());
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonICollectionOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonICollectionOfTConverter.cs
new file mode 100644
index 00000000000000..7a63ae126bcd1b
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonICollectionOfTConverter.cs
@@ -0,0 +1,108 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ ///
+ /// Converter for System.Collections.Generic.ICollection{TElement}.
+ ///
+ internal sealed class JsonICollectionOfTConverter
+ : JsonIEnumerableDefaultConverter
+ where TCollection : ICollection
+ {
+ protected override void Add(TElement value, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is ICollection);
+ ((ICollection)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.IsAssignableFrom(RuntimeType))
+ {
+ ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+ }
+
+ state.Current.ReturnValue = new List();
+ }
+ else
+ {
+ if (classInfo.CreateObject == null)
+ {
+ ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+ }
+
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
+
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
+ }
+
+ state.Current.ReturnValue = returnValue;
+ }
+ }
+
+ protected override bool OnWriteResume(
+ Utf8JsonWriter writer,
+ TCollection value,
+ JsonSerializerOptions options,
+ ref WriteStack state)
+ {
+ IEnumerator enumerator;
+ if (state.Current.CollectionEnumerator == null)
+ {
+ enumerator = value.GetEnumerator();
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ Debug.Assert(state.Current.CollectionEnumerator is IEnumerator);
+ enumerator = (IEnumerator)state.Current.CollectionEnumerator;
+ }
+
+ JsonConverter converter = GetElementConverter(ref state);
+ do
+ {
+ if (ShouldFlush(writer, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+
+ TElement element = enumerator.Current;
+ if (!converter.TryWrite(writer, element, options, ref state))
+ {
+ state.Current.CollectionEnumerator = enumerator;
+ return false;
+ }
+ } while (enumerator.MoveNext());
+
+ return true;
+ }
+
+ internal override Type RuntimeType
+ {
+ get
+ {
+ if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface)
+ {
+ return typeof(List);
+ }
+
+ return TypeToConvert;
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIDictionaryConverter.cs
new file mode 100644
index 00000000000000..814e1b9cc62d78
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonIDictionaryConverter.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ ///
+ /// Converter for System.Collections.IDictionary that (de)serializes as a JSON object with properties
+ /// representing the dictionary element key and value.
+ ///
+ internal sealed class JsonIDictionaryConverter
+ : JsonDictionaryDefaultConverter
+ where TCollection : IDictionary
+ {
+ protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
+ {
+ Debug.Assert(state.Current.ReturnValue is IDictionary);
+
+ string key = state.Current.JsonPropertyNameAsString!;
+ ((IDictionary)state.Current.ReturnValue!)[key] = value;
+ }
+
+ protected override void CreateCollection(ref ReadStack state)
+ {
+ JsonClassInfo classInfo = state.Current.JsonClassInfo;
+
+ if (TypeToConvert.IsInterface || TypeToConvert.IsAbstract)
+ {
+ if (!TypeToConvert.IsAssignableFrom(RuntimeType))
+ {
+ ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+ }
+
+ state.Current.ReturnValue = new Dictionary();
+ }
+ else
+ {
+ if (classInfo.CreateObject == null)
+ {
+ ThrowHelper.ThrowNotSupportedException_DeserializeNoParameterlessConstructor(TypeToConvert);
+ }
+
+ TCollection returnValue = (TCollection)classInfo.CreateObject()!;
+
+ if (returnValue.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(TypeToConvert);
+ }
+
+ state.Current.ReturnValue = returnValue;
+ }
+ }
+
+ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
+ {
+ IDictionaryEnumerator enumerator;
+ if (state.Current.CollectionEnumerator == null)
+ {
+ enumerator = value.GetEnumerator();
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ Debug.Assert(state.Current.CollectionEnumerator is IDictionaryEnumerator);
+ enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
+ }
+
+ JsonConverter