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 0dce72cc7b798b..e0539cac3a591f 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.cs
index 023fa4b1342afc..ca62e255a9d3ac 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.cs
@@ -32,15 +32,6 @@ private JsonEncodedText(byte[] utf8Value)
_utf8Value = utf8Value;
}
- private JsonEncodedText(string stringValue, byte[] utf8Value)
- {
- Debug.Assert(stringValue != null);
- Debug.Assert(utf8Value != null);
-
- _value = stringValue;
- _utf8Value = utf8Value;
- }
-
///
/// Encodes the string text value as a JSON string.
///
@@ -126,7 +117,7 @@ private static JsonEncodedText EncodeHelper(ReadOnlySpan utf8Value, JavaSc
if (idx != -1)
{
- return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
+ return new JsonEncodedText(JsonHelpers.EscapeValue(utf8Value, idx, encoder));
}
else
{
@@ -134,62 +125,6 @@ private static JsonEncodedText EncodeHelper(ReadOnlySpan utf8Value, JavaSc
}
}
- ///
- /// Internal version that keeps the existing string and byte[] references if there is no escaping required.
- ///
- internal static JsonEncodedText Encode(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder = null)
- {
- Debug.Assert(stringValue.Equals(JsonHelpers.Utf8GetString(utf8Value)));
-
- if (utf8Value.Length == 0)
- {
- return new JsonEncodedText(stringValue, utf8Value);
- }
-
- JsonWriterHelper.ValidateValue(utf8Value);
- return EncodeHelper(stringValue, utf8Value, encoder);
- }
-
- private static JsonEncodedText EncodeHelper(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder)
- {
- int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
-
- if (idx != -1)
- {
- return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
- }
- else
- {
- // Encoding is not necessary; use the same stringValue and utf8Value references.
- return new JsonEncodedText(stringValue, utf8Value);
- }
- }
-
- private static byte[] GetEscapedString(ReadOnlySpan utf8Value, int firstEscapeIndexVal, JavaScriptEncoder? encoder)
- {
- Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
- Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
-
- byte[]? valueArray = null;
-
- int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
-
- Span escapedValue = length <= JsonConstants.StackallocThreshold ?
- stackalloc byte[length] :
- (valueArray = ArrayPool.Shared.Rent(length));
-
- JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
-
- byte[] escapedString = escapedValue.Slice(0, written).ToArray();
-
- if (valueArray != null)
- {
- ArrayPool.Shared.Return(valueArray);
- }
-
- return escapedString;
- }
-
///
/// Determines whether this instance and another specified instance have the same value.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.cs
new file mode 100644
index 00000000000000..81679e400e2f0b
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text.Encodings.Web;
+
+namespace System.Text.Json
+{
+ internal static partial class JsonHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan utf8Value, JavaScriptEncoder? encoder)
+ {
+ int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
+
+ if (idx != -1)
+ {
+ return GetEscapedPropertyNameSection(utf8Value, idx, encoder);
+ }
+ else
+ {
+ return GetPropertyNameSection(utf8Value);
+ }
+ }
+
+ public static byte[] EscapeValue(
+ ReadOnlySpan utf8Value,
+ int firstEscapeIndexVal,
+ JavaScriptEncoder? encoder)
+ {
+ Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+ Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
+
+ byte[]? valueArray = null;
+
+ int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
+
+ Span escapedValue = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (valueArray = ArrayPool.Shared.Rent(length));
+
+ JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
+
+ byte[] escapedString = escapedValue.Slice(0, written).ToArray();
+
+ if (valueArray != null)
+ {
+ ArrayPool.Shared.Return(valueArray);
+ }
+
+ return escapedString;
+ }
+
+ private static byte[] GetEscapedPropertyNameSection(
+ ReadOnlySpan utf8Value,
+ int firstEscapeIndexVal,
+ JavaScriptEncoder? encoder)
+ {
+ Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+ Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
+
+ byte[]? valueArray = null;
+
+ int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
+
+ Span escapedValue = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (valueArray = ArrayPool.Shared.Rent(length));
+
+ JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
+
+ byte[] propertySection = GetPropertyNameSection(escapedValue.Slice(0, written));
+
+ if (valueArray != null)
+ {
+ ArrayPool.Shared.Return(valueArray);
+ }
+
+ return propertySection;
+ }
+
+ private static byte[] GetPropertyNameSection(ReadOnlySpan utf8Value)
+ {
+ int length = utf8Value.Length;
+ byte[] propertySection = new byte[length + 3];
+
+ propertySection[0] = JsonConstants.Quote;
+ utf8Value.CopyTo(propertySection.AsSpan(1, length));
+ propertySection[++length] = JsonConstants.Quote;
+ propertySection[++length] = JsonConstants.KeyValueSeperator;
+
+ return propertySection;
+ }
+ }
+}
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 ce687afa263edb..9df93bd1fc9d9a 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
@@ -91,7 +91,7 @@ public static void ReadWithVerify(this ref Utf8JsonReader reader)
///
/// The utf8 bytes to convert.
///
- internal static string Utf8GetString(ReadOnlySpan bytes)
+ public static string Utf8GetString(ReadOnlySpan bytes)
{
return Encoding.UTF8.GetString(bytes
#if NETSTANDARD2_0 || NETFRAMEWORK
@@ -103,7 +103,7 @@ internal static string Utf8GetString(ReadOnlySpan bytes)
///
/// Emulates Dictionary.TryAdd on netstandard.
///
- internal static bool TryAdd(Dictionary dictionary, TKey key, TValue value) where TKey : notnull
+ public static bool TryAdd(Dictionary dictionary, in TKey key, in TValue value) where TKey : notnull
{
#if NETSTANDARD2_0 || NETFRAMEWORK
if (!dictionary.ContainsKey(key))
@@ -118,7 +118,7 @@ internal static bool TryAdd(Dictionary dictionary, T
#endif
}
- internal static bool IsFinite(double value)
+ public static bool IsFinite(double value)
{
#if BUILDING_INBOX_LIBRARY
return double.IsFinite(value);
@@ -127,7 +127,7 @@ internal static bool IsFinite(double value)
#endif
}
- internal static bool IsFinite(float value)
+ public static bool IsFinite(float value)
{
#if BUILDING_INBOX_LIBRARY
return float.IsFinite(value);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ArrayConverter.cs
index 70d0c611dd5ba3..4b4da7b0c8d292 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ArrayConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ArrayConverter.cs
@@ -16,7 +16,7 @@ internal sealed class ArrayConverter
{
internal override bool CanHaveIdMetadata => false;
- protected override void Add(TElement value, ref ReadStack state)
+ protected override void Add(in TElement value, ref ReadStack state)
{
((List)state.Current.ReturnValue!).Add(value);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs
index aa54f0956e41e5..d83a06d84308e5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs
@@ -11,7 +11,7 @@ internal sealed class ConcurrentQueueOfTConverter
: IEnumerableDefaultConverter
where TCollection : ConcurrentQueue
{
- protected override void Add(TElement value, ref ReadStack state)
+ protected override void Add(in TElement value, ref ReadStack state)
{
((TCollection)state.Current.ReturnValue!).Enqueue(value);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs
index 9ba01b68df2ddf..9d6422adcaa6a9 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs
@@ -11,7 +11,7 @@ internal sealed class ConcurrentStackOfTConverter
: IEnumerableDefaultConverter
where TCollection : ConcurrentStack
{
- protected override void Add(TElement value, ref ReadStack state)
+ protected override void Add(in TElement value, ref ReadStack state)
{
((TCollection)state.Current.ReturnValue!).Push(value);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
index 77c8ff882159ec..1c4b0fae4ff128 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
@@ -16,7 +16,7 @@ internal abstract class DictionaryDefaultConverter
///
/// When overridden, adds the value to the collection.
///
- protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
+ protected abstract void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state);
///
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs
index 14ecb4526999cc..13bcea6c7d10bd 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs
@@ -14,7 +14,7 @@ internal sealed class DictionaryOfStringTValueConverter
: DictionaryDefaultConverter
where TCollection : Dictionary
{
- protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
+ protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs
index cb1b66ac18371f..c059a767818034 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs
@@ -13,7 +13,7 @@ internal sealed class ICollectionOfTConverter
: IEnumerableDefaultConverter
where TCollection : ICollection
{
- protected override void Add(TElement value, ref ReadStack state)
+ protected override void Add(in TElement value, ref ReadStack state)
{
((ICollection)state.Current.ReturnValue!).Add(value);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
index 6ef56753427f4e..4927187b0af77f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
@@ -15,7 +15,7 @@ internal sealed class IDictionaryConverter
: DictionaryDefaultConverter
where TCollection : IDictionary
{
- protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
+ protected override void Add(in object? value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((IDictionary)state.Current.ReturnValue!)[key] = value;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs
index 6115fada07decf..72a12ade8ab00f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs
@@ -14,7 +14,7 @@ internal sealed class IDictionaryOfStringTValueConverter
: DictionaryDefaultConverter
where TCollection : IDictionary
{
- protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
+ protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverter.cs
index 52da1f4e0d0e5f..8bd342e4cebee3 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverter.cs
@@ -15,7 +15,7 @@ internal sealed class IEnumerableConverter
: IEnumerableDefaultConverter
where TCollection : IEnumerable
{
- protected override void Add(object? value, ref ReadStack state)
+ protected override void Add(in object? value, ref ReadStack state)
{
((List
- // AggressiveInlining used although a large method it is only called from two locations and is on a hot path.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static JsonPropertyInfo LookupProperty(
object obj,
- ref Utf8JsonReader reader,
- JsonSerializerOptions options,
+ ReadOnlySpan unescapedPropertyName,
ref ReadStack state,
out bool useExtensionProperty,
bool createExtensionProperty = true)
@@ -29,8 +26,6 @@ internal static JsonPropertyInfo LookupProperty(
useExtensionProperty = false;
- ReadOnlySpan unescapedPropertyName = GetPropertyName(ref state, ref reader, options);
-
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(
unescapedPropertyName,
ref state.Current,
@@ -113,7 +108,7 @@ internal static void CreateDataExtensionProperty(
Debug.Assert(genericArgs.Length == 2);
Debug.Assert(genericArgs[0].UnderlyingSystemType == typeof(string));
Debug.Assert(
- genericArgs[1].UnderlyingSystemType == typeof(object) ||
+ genericArgs[1].UnderlyingSystemType == JsonClassInfo.ObjectType ||
genericArgs[1].UnderlyingSystemType == typeof(JsonElement));
#endif
if (jsonPropertyInfo.RuntimeClassInfo.CreateObject == null)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
index af50b9166597a7..cf948b2a694452 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
@@ -53,7 +53,7 @@ public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSer
return WriteCoreBytes(value!, inputType, options);
}
- private static byte[] WriteCoreBytes(TValue value, Type inputType, JsonSerializerOptions? options)
+ private static byte[] WriteCoreBytes(in TValue value, Type inputType, JsonSerializerOptions? options)
{
if (options == null)
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
index a6d134b61db010..1b8c7929e6cc18 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
@@ -9,19 +9,22 @@ namespace System.Text.Json
{
public static partial class JsonSerializer
{
- private static void WriteCore(Utf8JsonWriter writer, TValue value, Type inputType, JsonSerializerOptions options)
+ private static void WriteCore(
+ Utf8JsonWriter writer,
+ in TValue value,
+ Type inputType,
+ JsonSerializerOptions options)
{
Debug.Assert(writer != null);
// We treat typeof(object) special and allow polymorphic behavior.
- if (inputType == typeof(object) && value != null)
+ if (value != null && inputType == JsonClassInfo.ObjectType)
{
inputType = value!.GetType();
}
WriteStack state = default;
- state.Initialize(inputType, options, supportContinuation: false);
- JsonConverter jsonConverter = state.Current.JsonClassInfo!.PropertyInfoForClassInfo.ConverterBase;
+ JsonConverter jsonConverter = state.Initialize(inputType, options, supportContinuation: false);
bool success = WriteCore(jsonConverter, writer, value, options, ref state);
Debug.Assert(success);
@@ -30,7 +33,7 @@ private static void WriteCore(Utf8JsonWriter writer, TValue value, Type
private static bool WriteCore(
JsonConverter jsonConverter,
Utf8JsonWriter writer,
- TValue value,
+ in TValue value,
JsonSerializerOptions options,
ref WriteStack state)
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
index 4ab037a7c2b464..07856fed8faf62 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
@@ -108,15 +108,13 @@ private static async Task WriteAsyncCore(
using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
{
// We treat typeof(object) special and allow polymorphic behavior.
- if (inputType == typeof(object) && value != null)
+ if (inputType == JsonClassInfo.ObjectType && value != null)
{
inputType = value!.GetType();
}
WriteStack state = default;
- state.Initialize(inputType, options, supportContinuation: true);
-
- JsonConverter converterBase = state.Current.JsonClassInfo!.PropertyInfoForClassInfo.ConverterBase;
+ JsonConverter converterBase = state.Initialize(inputType, options, supportContinuation: true);
bool isFinalBlock;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
index c195de8414d693..b09b1e68e618b9 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
@@ -58,7 +58,7 @@ public static string Serialize(object? value, Type inputType, JsonSerializerOpti
return Serialize(value, inputType, options);
}
- private static string Serialize(TValue value, Type inputType, JsonSerializerOptions? options)
+ private static string Serialize(in TValue value, Type inputType, JsonSerializerOptions? options)
{
if (options == null)
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs
index 6b364dd4ed5d3a..c9a5c31dea02bc 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs
@@ -59,7 +59,7 @@ public static void Serialize(Utf8JsonWriter writer, object? value, Type inputTyp
Serialize(writer, value, inputType, options);
}
- private static void Serialize(Utf8JsonWriter writer, TValue value, Type type, JsonSerializerOptions? options)
+ private static void Serialize(Utf8JsonWriter writer, in TValue value, Type type, JsonSerializerOptions? options)
{
if (options == null)
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
index 98587cb9f0c99a..66666db8396286 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
@@ -21,6 +21,10 @@ public sealed partial class JsonSerializerOptions
private readonly ConcurrentDictionary _classes = new ConcurrentDictionary();
+ // Simple LRU cache for the public (de)serialize entry points that avoid some lookups in _classes.
+ // Although this may be written by multiple threads, 'volatile' was not added since any local affinity is fine.
+ private JsonClassInfo? _lastClass { get; set; }
+
// For any new option added, adding it to the options copied in the copy constructor below must be considered.
private MemberAccessor? _memberAccessorStrategy;
@@ -447,6 +451,22 @@ internal JsonClassInfo GetOrAddClass(Type type)
return result;
}
+ ///
+ /// Return the ClassInfo for root API calls.
+ /// This has a LRU cache that is intended only for public API calls that specify the root type.
+ ///
+ internal JsonClassInfo GetOrAddClassForRootType(Type type)
+ {
+ JsonClassInfo? jsonClassInfo = _lastClass;
+ if (jsonClassInfo?.Type != type)
+ {
+ jsonClassInfo = GetOrAddClass(type);
+ _lastClass = jsonClassInfo;
+ }
+
+ return jsonClassInfo;
+ }
+
internal bool TypeIsCached(Type type)
{
return _classes.ContainsKey(type);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
index 80db2c446e7907..afa5fb127d45d8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
@@ -82,8 +82,7 @@ private void AddCurrent()
public void Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
{
- JsonClassInfo jsonClassInfo = options.GetOrAddClass(type);
-
+ JsonClassInfo jsonClassInfo = options.GetOrAddClassForRootType(type);
Current.JsonClassInfo = jsonClassInfo;
// The initial JsonPropertyInfo will be used to obtain the converter.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
index a5d53eee033c13..e5cd65d906a6cc 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
@@ -31,7 +31,7 @@ internal sealed class ReflectionEmitMemberAccessor : MemberAccessor
var dynamicMethod = new DynamicMethod(
ConstructorInfo.ConstructorName,
- typeof(object),
+ JsonClassInfo.ObjectType,
Type.EmptyTypes,
typeof(ReflectionEmitMemberAccessor).Module,
skipVisibility: true);
@@ -158,7 +158,7 @@ private static DynamicMethod CreateAddMethodDelegate(Type collectionType)
var dynamicMethod = new DynamicMethod(
realMethod.Name,
typeof(void),
- new[] { collectionType, typeof(object) },
+ new[] { collectionType, JsonClassInfo.ObjectType },
typeof(ReflectionEmitMemberAccessor).Module,
skipVisibility: true);
@@ -226,7 +226,7 @@ private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type c
private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Type classType, Type propertyType)
{
MethodInfo? realMethod = propertyInfo.GetMethod;
- Type objectType = typeof(object);
+ Type objectType = JsonClassInfo.ObjectType;
Debug.Assert(realMethod != null);
var dynamicMethod = new DynamicMethod(
@@ -262,7 +262,7 @@ private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Typ
private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type classType, Type propertyType)
{
MethodInfo? realMethod = propertyInfo.SetMethod;
- Type objectType = typeof(object);
+ Type objectType = JsonClassInfo.ObjectType;
Debug.Assert(realMethod != null);
var dynamicMethod = new DynamicMethod(
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs
index b0f94e4538b3a3..a56a708eb1a3f0 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs
@@ -113,7 +113,7 @@ public override JsonClassInfo.ParameterizedConstructorDelegate CreateAddMethodDelegate()
{
Type collectionType = typeof(TCollection);
- Type elementType = typeof(object);
+ Type elementType = JsonClassInfo.ObjectType;
// We verified this won't be null when we created the converter for the collection type.
MethodInfo addMethod = (collectionType.GetMethod("Push") ?? collectionType.GetMethod("Enqueue"))!;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
index e2efb6492c24d3..283dd60f78367f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
@@ -66,10 +66,9 @@ private void AddCurrent()
///
/// Initialize the state without delayed initialization of the JsonClassInfo.
///
- public void Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
+ public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
{
- JsonClassInfo jsonClassInfo = options.GetOrAddClass(type);
-
+ JsonClassInfo jsonClassInfo = options.GetOrAddClassForRootType(type);
Current.JsonClassInfo = jsonClassInfo;
if ((jsonClassInfo.ClassType & (ClassType.Enumerable | ClassType.Dictionary)) == 0)
@@ -77,13 +76,14 @@ public void Initialize(Type type, JsonSerializerOptions options, bool supportCon
Current.DeclaredJsonPropertyInfo = jsonClassInfo.PropertyInfoForClassInfo;
}
- bool preserveReferences = options.ReferenceHandler != null;
- if (preserveReferences)
+ if (options.ReferenceHandler != null)
{
ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: true);
}
SupportContinuation = supportContinuation;
+
+ return jsonClassInfo.PropertyInfoForClassInfo.ConverterBase;
}
public void Push()
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
index 152f454c625024..c5772f978005d5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
@@ -4,6 +4,7 @@
using System.Buffers;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace System.Text.Json
{
@@ -22,6 +23,29 @@ public void WriteNull(JsonEncodedText propertyName)
_tokenType = JsonTokenType.Null;
}
+ internal void WriteNullSection(ReadOnlySpan escapedPropertyNameSection)
+ {
+ if (_options.Indented)
+ {
+ ReadOnlySpan escapedName =
+ escapedPropertyNameSection.Slice(1, escapedPropertyNameSection.Length - 3);
+
+ WriteLiteralHelper(escapedName, JsonConstants.NullValue);
+ _tokenType = JsonTokenType.Null;
+ }
+ else
+ {
+ Debug.Assert(escapedPropertyNameSection.Length <= JsonConstants.MaxUnescapedTokenSize - 3);
+
+ ReadOnlySpan span = JsonConstants.NullValue;
+
+ WriteLiteralSection(escapedPropertyNameSection, span);
+
+ SetFlagToAddListSeparatorBeforeNextItem();
+ _tokenType = JsonTokenType.Null;
+ }
+ }
+
private void WriteLiteralHelper(ReadOnlySpan utf8PropertyName, ReadOnlySpan value)
{
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);
@@ -360,6 +384,34 @@ private void WriteLiteralMinimized(ReadOnlySpan escapedPropertyName, ReadO
BytesPending += value.Length;
}
+ // AggressiveInlining used since this is only called from one location.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void WriteLiteralSection(ReadOnlySpan escapedPropertyNameSection, ReadOnlySpan value)
+ {
+ Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
+ Debug.Assert(escapedPropertyNameSection.Length < int.MaxValue - value.Length - 1);
+
+ int minRequired = escapedPropertyNameSection.Length + value.Length;
+ int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span output = _memory.Span;
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+
+ escapedPropertyNameSection.CopyTo(output.Slice(BytesPending));
+ BytesPending += escapedPropertyNameSection.Length;
+
+ value.CopyTo(output.Slice(BytesPending));
+ BytesPending += value.Length;
+ }
+
private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value)
{
int indent = Indentation;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
index 74ebc60214ff91..ddcca39fa28f4d 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
@@ -4,6 +4,7 @@
using System.Buffers;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace System.Text.Json
{
@@ -19,6 +20,26 @@ public sealed partial class Utf8JsonWriter
public void WritePropertyName(JsonEncodedText propertyName)
=> WritePropertyNameHelper(propertyName.EncodedUtf8Bytes);
+ internal void WritePropertyNameSection(ReadOnlySpan escapedPropertyNameSection)
+ {
+ if (_options.Indented)
+ {
+ ReadOnlySpan escapedPropertyName =
+ escapedPropertyNameSection.Slice(1, escapedPropertyNameSection.Length - 3);
+
+ WritePropertyNameHelper(escapedPropertyName);
+ }
+ else
+ {
+ Debug.Assert(escapedPropertyNameSection.Length <= JsonConstants.MaxUnescapedTokenSize - 3);
+
+ WriteStringPropertyNameSection(escapedPropertyNameSection);
+
+ _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+ _tokenType = JsonTokenType.PropertyName;
+ }
+ }
+
private void WritePropertyNameHelper(ReadOnlySpan utf8PropertyName)
{
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);
@@ -285,6 +306,8 @@ private void WriteStringByOptionsPropertyName(ReadOnlySpan utf8PropertyNam
}
}
+ // AggressiveInlining used since this is only called from one location.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteStringMinimizedPropertyName(ReadOnlySpan escapedPropertyName)
{
Debug.Assert(escapedPropertyName.Length <= JsonConstants.MaxEscapedTokenSize);
@@ -313,6 +336,33 @@ private void WriteStringMinimizedPropertyName(ReadOnlySpan escapedProperty
output[BytesPending++] = JsonConstants.KeyValueSeperator;
}
+ // AggressiveInlining used since this is only called from one location.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void WriteStringPropertyNameSection(ReadOnlySpan escapedPropertyNameSection)
+ {
+ Debug.Assert(escapedPropertyNameSection.Length <= JsonConstants.MaxEscapedTokenSize - 3);
+ Debug.Assert(escapedPropertyNameSection.Length < int.MaxValue - 4);
+
+ int maxRequired = escapedPropertyNameSection.Length + 1; // Optionally, 1 list separator
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+
+ escapedPropertyNameSection.CopyTo(output.Slice(BytesPending));
+ BytesPending += escapedPropertyNameSection.Length;
+ }
+
+ // AggressiveInlining used since this is only called from one location.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyName)
{
int indent = Indentation;
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs
index 675d5a4cf93a06..c879f5893cb842 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs
@@ -12,18 +12,18 @@ namespace System.Text.Json.Serialization.Tests
public static class CacheTests
{
[Fact, OuterLoop]
- public static void MultipleThreadsLooping()
+ public static void MultipleThreads_SameType_DifferentJson_Looping()
{
const int Iterations = 100;
for (int i = 0; i < Iterations; i++)
{
- MultipleThreads();
+ MultipleThreads_SameType_DifferentJson();
}
}
[Fact]
- public static void MultipleThreads()
+ public static void MultipleThreads_SameType_DifferentJson()
{
// Use local options to avoid obtaining already cached metadata from the default options.
var options = new JsonSerializerOptions();
@@ -73,6 +73,58 @@ void SerializeObject()
Task.WaitAll(tasks);
}
+ [Fact, OuterLoop]
+ public static void MultipleThreads_DifferentTypes_Looping()
+ {
+ const int Iterations = 100;
+
+ for (int i = 0; i < Iterations; i++)
+ {
+ MultipleThreads_DifferentTypes();
+ }
+ }
+
+ [Fact]
+ public static void MultipleThreads_DifferentTypes()
+ {
+ // Use local options to avoid obtaining already cached metadata from the default options.
+ var options = new JsonSerializerOptions();
+
+ const int TestClassCount = 2;
+
+ var testObjects = new ITestClass[TestClassCount]
+ {
+ new SimpleTestClassWithNulls(),
+ new SimpleTestClass(),
+ };
+
+ foreach (ITestClass obj in testObjects)
+ {
+ obj.Initialize();
+ }
+
+ void Test(int i)
+ {
+ Type testClassType = testObjects[i].GetType();
+
+ string json = JsonSerializer.Serialize(testObjects[i], testClassType, options);
+
+ ITestClass obj = (ITestClass)JsonSerializer.Deserialize(json, testClassType, options);
+ obj.Verify();
+ };
+
+ const int OuterCount = 12;
+ Task[] tasks = new Task[OuterCount * TestClassCount];
+
+ for (int i = 0; i < tasks.Length; i += TestClassCount)
+ {
+ tasks[i + 0] = Task.Run(() => Test(TestClassCount - 1));
+ tasks[i + 1] = Task.Run(() => Test(TestClassCount - 2));
+ }
+
+ Task.WaitAll(tasks);
+ }
+
[Fact]
public static void PropertyCacheWithMinInputsFirst()
{