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)state.Current.ReturnValue!).Add(value); } @@ -62,7 +62,8 @@ protected override bool OnWriteResume( return false; } - if (!converter.TryWrite(writer, enumerator.Current, options, ref state)) + object? element = enumerator.Current; + if (!converter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs index 4bd548f9a747c2..61ca581a82b1be 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs @@ -46,7 +46,7 @@ internal static class IEnumerableConverterFactoryHelpers Type? baseTypeToCheck = type; - while (baseTypeToCheck != null && baseTypeToCheck != typeof(object)) + while (baseTypeToCheck != null && baseTypeToCheck != JsonClassInfo.ObjectType) { if (baseTypeToCheck.IsGenericType) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableDefaultConverter.cs index 9dd215a4287e1d..e53f9ebc0ec472 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableDefaultConverter.cs @@ -13,7 +13,7 @@ namespace System.Text.Json.Serialization.Converters internal abstract class IEnumerableDefaultConverter : JsonCollectionConverter { - protected abstract void Add(TElement value, ref ReadStack state); + protected abstract void Add(in TElement value, ref ReadStack state); protected abstract void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options); protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { } @@ -242,7 +242,11 @@ internal override bool OnTryRead( return true; } - internal sealed override 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; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs index 26e83873ec24dd..fc0e5a35034c65 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs @@ -14,7 +14,7 @@ internal sealed class IEnumerableOfTConverter : IEnumerableDefaultConverter where TCollection : IEnumerable { - 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/IEnumerableWithAddMethodConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableWithAddMethodConverter.cs index b3121146d449f4..34344f5024b709 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableWithAddMethodConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableWithAddMethodConverter.cs @@ -10,7 +10,7 @@ internal sealed class IEnumerableWithAddMethodConverter : IEnumerableDefaultConverter where TCollection : IEnumerable { - protected override void Add(object? value, ref ReadStack state) + protected override void Add(in object? value, ref ReadStack state) { ((Action)state.Current.AddMethodDelegate!)((TCollection)state.Current.ReturnValue!, value); } @@ -53,7 +53,8 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, return false; } - if (!converter.TryWrite(writer, enumerator.Current, options, ref state)) + object? element = enumerator.Current; + if (!converter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListConverter.cs index b9131d7a2cfc2e..8b518a578b2a8c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListConverter.cs @@ -12,7 +12,7 @@ internal sealed class IListConverter : IEnumerableDefaultConverter where TCollection : IList { - protected override void Add(object? value, ref ReadStack state) + protected override void Add(in object? value, ref ReadStack state) { ((IList)state.Current.ReturnValue!).Add(value); } @@ -74,7 +74,6 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, } object? element = enumerator.Current; - if (!converter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs index c349280bbb4e4e..90d38ab94702b0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs @@ -13,7 +13,7 @@ internal sealed class IListOfTConverter : IEnumerableDefaultConverter where TCollection : IList { - protected override void Add(TElement value, ref ReadStack state) + protected override void Add(in TElement value, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!).Add(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs index a4552cba9a2713..0d05ff3a1869de 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs @@ -10,7 +10,7 @@ internal sealed class IReadOnlyDictionaryOfStringTValueConverter where TCollection : IReadOnlyDictionary { - 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!; ((Dictionary)state.Current.ReturnValue!)[key] = value; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs index 29645584b3536d..d956a3c189bb55 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs @@ -10,7 +10,7 @@ internal sealed class ISetOfTConverter : IEnumerableDefaultConverter where TCollection : ISet { - protected override void Add(TElement value, ref ReadStack state) + protected override void Add(in TElement value, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!).Add(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs index 9cde8e4233f283..4e6e1caa818f38 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs @@ -10,7 +10,7 @@ internal sealed class ImmutableDictionaryOfStringTValueConverter where TCollection : IReadOnlyDictionary { - 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!; ((Dictionary)state.Current.ReturnValue!)[key] = value; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs index cfa1f0bf3d60f4..aa42aff5d4fcca 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs @@ -10,7 +10,7 @@ internal sealed class ImmutableEnumerableOfTConverter : IEnumerableDefaultConverter where TCollection : IEnumerable { - 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/ListOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ListOfTConverter.cs index a977fededc3ae6..bf7a6ef22c0860 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ListOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ListOfTConverter.cs @@ -11,7 +11,7 @@ internal sealed class ListOfTConverter : IEnumerableDefaultConverter where TCollection: List { - protected override void Add(TElement value, ref ReadStack state) + protected override void Add(in TElement value, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!).Add(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/QueueOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/QueueOfTConverter.cs index dfea6e14808441..1193d85e2643a7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/QueueOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/QueueOfTConverter.cs @@ -10,7 +10,7 @@ internal sealed class QueueOfTConverter : IEnumerableDefaultConverter where TCollection : Queue { - 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/StackOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs index 5725e7f1a04f47..0dcbbaa4a57006 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs @@ -10,7 +10,7 @@ internal sealed class StackOfTConverter : IEnumerableDefaultConverter where TCollection : Stack { - 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/Object/ObjectConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs index 813969989e32f9..061e0f33423263 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs @@ -43,7 +43,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer if (parameterCount <= JsonConstants.UnboxedParameterCountThreshold) { - Type placeHolderType = typeof(object); + Type placeHolderType = JsonClassInfo.ObjectType; Type[] typeArguments = new Type[JsonConstants.UnboxedParameterCountThreshold + 1]; typeArguments[0] = typeToConvert; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index c6624a33a75181..0a6fe7c2b70c86 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System.Text.Json.Serialization.Converters { @@ -48,10 +49,10 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, // Read method would have thrown if otherwise. Debug.Assert(tokenType == JsonTokenType.PropertyName); + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty( obj, - ref reader, - options, + unescapedPropertyName, ref state, out bool useExtensionProperty); @@ -152,10 +153,10 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, // Read method would have thrown if otherwise. Debug.Assert(tokenType == JsonTokenType.PropertyName); + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); jsonPropertyInfo = JsonSerializer.LookupProperty( obj, - ref reader, - options, + unescapedPropertyName, ref state, out bool useExtensionProperty); @@ -229,7 +230,11 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, return true; } - internal sealed override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) + internal sealed override bool OnTryWrite( + Utf8JsonWriter writer, + T value, + JsonSerializerOptions options, + ref WriteStack state) { // Minimize boxing for structs by only boxing once here object objectValue = value!; @@ -356,6 +361,8 @@ internal sealed override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSer } } + // AggressiveInlining since this method is only called from two locations and is on a hot path. + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ReadPropertyValue( object obj, ref ReadStack state, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs index 89ecc996e11a54..4c1a0a7fd1d08a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs @@ -188,10 +188,10 @@ private void ReadConstructorArguments(ref ReadStack state, ref Utf8JsonReader re } else { + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty( obj: null!, - ref reader, - options, + unescapedPropertyName, ref state, out _, createExtensionProperty: false); @@ -273,10 +273,10 @@ private bool ReadConstructorArgumentsWithContinuation(ref ReadStack state, ref U } else { + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); jsonPropertyInfo = JsonSerializer.LookupProperty( obj: null!, - ref reader, - options, + unescapedPropertyName, ref state, out bool useExtensionProperty, createExtensionProperty: false); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs index 10f78579c164e0..5ef01ee542b1d8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs @@ -14,6 +14,11 @@ namespace System.Text.Json [DebuggerDisplay("ClassType.{ClassType}, {Type.Name}")] internal sealed partial class JsonClassInfo { + /// + /// Cached typeof(object). It is faster to cache this than to call typeof(object) multiple times. + /// + public static readonly Type ObjectType = typeof(object); + // The length of the property name embedded in the key (in bytes). // The key is a ulong (8 bytes) containing the first 7 bytes of the property name // followed by a byte representing the length. @@ -115,7 +120,7 @@ internal static JsonPropertyInfo CreatePropertyInfoForClassInfo( declaredPropertyType: declaredPropertyType, runtimePropertyType: runtimePropertyType, propertyInfo: null, // Not a real property so this is null. - parentClassType: typeof(object), // a dummy value (not used) + parentClassType: JsonClassInfo.ObjectType, // a dummy value (not used) converter: converter, options); @@ -442,18 +447,10 @@ public static ulong GetKey(ReadOnlySpan propertyName) if (length > 7) { - key = MemoryMarshal.Read(propertyName); - - // Max out the length byte. - // This will cause the comparison logic to always test for equality against the full contents - // when the first 7 bytes are the same. - key |= 0xFF00000000000000; - - // It is also possible to include the length up to 0xFF in order to prevent false positives - // when the first 7 bytes match but a different length (up to 0xFF). However the extra logic - // slows key generation in the majority of cases: - // key &= 0x00FFFFFFFFFFFFFF; - // key |= (ulong) 7 << Math.Max(length, 0xFF); + key = MemoryMarshal.Read(propertyName) + & 0x00FFFFFFFFFFFFFF + // Include the length with a max of 0xFF. + | ((ulong)Math.Min(length, 0xFF)) << (7 * BitsInByte); } else if (length > 3) { @@ -509,13 +506,17 @@ public static ulong GetKey(ReadOnlySpan propertyName) // Verify key contains the embedded bytes as expected. Debug.Assert( - (length < 1 || propertyName[0] == ((key & ((ulong)0xFF << 8 * 0)) >> 8 * 0)) && - (length < 2 || propertyName[1] == ((key & ((ulong)0xFF << 8 * 1)) >> 8 * 1)) && - (length < 3 || propertyName[2] == ((key & ((ulong)0xFF << 8 * 2)) >> 8 * 2)) && - (length < 4 || propertyName[3] == ((key & ((ulong)0xFF << 8 * 3)) >> 8 * 3)) && - (length < 5 || propertyName[4] == ((key & ((ulong)0xFF << 8 * 4)) >> 8 * 4)) && - (length < 6 || propertyName[5] == ((key & ((ulong)0xFF << 8 * 5)) >> 8 * 5)) && - (length < 7 || propertyName[6] == ((key & ((ulong)0xFF << 8 * 6)) >> 8 * 6))); + // Verify embedded property name. + (length < 1 || propertyName[0] == ((key & ((ulong)0xFF << BitsInByte * 0)) >> BitsInByte * 0)) && + (length < 2 || propertyName[1] == ((key & ((ulong)0xFF << BitsInByte * 1)) >> BitsInByte * 1)) && + (length < 3 || propertyName[2] == ((key & ((ulong)0xFF << BitsInByte * 2)) >> BitsInByte * 2)) && + (length < 4 || propertyName[3] == ((key & ((ulong)0xFF << BitsInByte * 3)) >> BitsInByte * 3)) && + (length < 5 || propertyName[4] == ((key & ((ulong)0xFF << BitsInByte * 4)) >> BitsInByte * 4)) && + (length < 6 || propertyName[5] == ((key & ((ulong)0xFF << BitsInByte * 5)) >> BitsInByte * 5)) && + (length < 7 || propertyName[6] == ((key & ((ulong)0xFF << BitsInByte * 6)) >> BitsInByte * 6)) && + // Verify embedded length. + (length >= 0xFF || (key & ((ulong)0xFF << BitsInByte * 7)) >> BitsInByte * 7 == (ulong)length) && + (length < 0xFF || (key & ((ulong)0xFF << BitsInByte * 7)) >> BitsInByte * 7 == 0xFF)); return key; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 631d47ddc0161e..4f0e8a512532a0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -170,7 +170,7 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. - cache.Remove(DataExtensionProperty!.NameAsString!); + cache.Remove(DataExtensionProperty!.NameAsString); cacheArray = new JsonPropertyInfo[cache.Count + 1]; @@ -265,10 +265,10 @@ private void InitializeConstructorParameters(Dictionary= options.EffectiveMaxDepth) { @@ -268,7 +268,7 @@ internal bool TryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions opt } Type type = value.GetType(); - if (type == typeof(object)) + if (type == JsonClassInfo.ObjectType) { writer.WriteStartObject(); writer.WriteEndObject(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 9a19769ac15ab2..471321029c82af 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.Json.Serialization; namespace System.Text.Json @@ -23,9 +24,13 @@ internal abstract class JsonPropertyInfo public static JsonPropertyInfo GetPropertyPlaceholder() { JsonPropertyInfo info = new JsonPropertyInfo(); + Debug.Assert(!info.IsForClassInfo); Debug.Assert(!info.ShouldDeserialize); Debug.Assert(!info.ShouldSerialize); + + info.NameAsString = string.Empty; + return info; } @@ -82,11 +87,8 @@ private void DeterminePropertyName() Debug.Assert(NameAsString != null); - // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder. NameAsUtf8Bytes = Encoding.UTF8.GetBytes(NameAsString); - - // Cache the escaped property name. - EscapedName = JsonEncodedText.Encode(NameAsString, NameAsUtf8Bytes, Options.Encoder); + EscapedNameSection = JsonHelpers.GetEscapedPropertyNameSection(NameAsUtf8Bytes, Options.Encoder); } private void DetermineSerializationCapabilities(JsonIgnoreCondition? ignoreCondition) @@ -203,8 +205,7 @@ public virtual void Initialize( // There are 3 copies of the property name: // 1) NameAsString. The unescaped property name. // 2) NameAsUtf8Bytes. The Utf8 version of NameAsString. Used during during deserialization for property lookup. - // 3) EscapedName. The escaped verson of NameAsString and NameAsUtf8Bytes written during serialization. Internally shares - // the same instances of NameAsString and NameAsUtf8Bytes if there is no escaping. + // 3) EscapedNameSection. The escaped verson of NameAsUtf8Bytes plus the wrapping quotes and a trailing colon. Used during serialization. /// /// The unescaped name of the property. @@ -212,20 +213,17 @@ public virtual void Initialize( /// the value specified in JsonPropertyNameAttribute, /// or the value returned from PropertyNamingPolicy(clrPropertyName). /// - public string? NameAsString { get; private set; } + public string NameAsString { get; private set; } = null!; /// /// Utf8 version of NameAsString. /// - public byte[]? NameAsUtf8Bytes { get; private set; } + public byte[] NameAsUtf8Bytes = null!; /// /// The escaped name passed to the writer. /// - /// - /// JsonEncodedText is a value type so a field is used (not a property) to avoid unnecessary copies. - /// - public JsonEncodedText? EscapedName; + public byte[] EscapedNameSection = null!; // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions. protected JsonSerializerOptions Options { get; set; } = null!; // initialized in Init method @@ -245,7 +243,7 @@ public bool ReadJsonAndAddExtensionProperty(object obj, ref ReadStack state, ref } else { - JsonConverter converter = (JsonConverter)Options.GetConverter(typeof(object)); + JsonConverter converter = (JsonConverter)Options.GetConverter(JsonClassInfo.ObjectType); if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out object? value)) { @@ -283,7 +281,7 @@ public bool ReadJsonExtensionDataValue(ref ReadStack state, ref Utf8JsonReader r { Debug.Assert(this == state.Current.JsonClassInfo.DataExtensionProperty); - if (RuntimeClassInfo.ElementType == typeof(object) && reader.TokenType == JsonTokenType.Null) + if (RuntimeClassInfo.ElementType == JsonClassInfo.ObjectType && reader.TokenType == JsonTokenType.Null) { value = null; return true; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs index fc2f25a0062b63..08edd430c0a4aa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs @@ -97,8 +97,6 @@ public override JsonConverter ConverterBase public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf8JsonWriter writer) { - Debug.Assert(EscapedName.HasValue); - bool success; T value = Get!(obj); @@ -111,7 +109,7 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf { if (!Converter.HandleNull) { - writer.WriteNull(EscapedName.Value); + writer.WriteNullSection(EscapedNameSection); } else { @@ -121,7 +119,7 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - writer.WritePropertyName(EscapedName.Value); + writer.WritePropertyNameSection(EscapedNameSection); } int originalDepth = writer.CurrentDepth; @@ -143,7 +141,7 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - writer.WritePropertyName(EscapedName.Value); + writer.WritePropertyNameSection(EscapedNameSection); } success = Converter.TryWrite(writer, value, Options, ref state); @@ -193,7 +191,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U } else if (Converter.CanUseDirectReadOrWrite) { - if (!(isNullToken && IgnoreDefaultValuesOnRead && Converter.CanBeNull)) + if (!isNullToken || !IgnoreDefaultValuesOnRead || !Converter.CanBeNull) { // Optimize for internal converters by avoiding the extra call to TryRead. T fastvalue = Converter.Read(ref reader, RuntimePropertyType!, Options); @@ -205,8 +203,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U else { success = true; - - if (!(isNullToken && IgnoreDefaultValuesOnRead && Converter.CanBeNull)) + if (!isNullToken || !IgnoreDefaultValuesOnRead || !Converter.CanBeNull) { success = Converter.TryRead(ref reader, RuntimePropertyType!, Options, ref state, out T value); if (success) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index 7ed9da9a775c3f..cfb8ebb32a8b60 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -15,12 +15,9 @@ public static partial class JsonSerializer /// Lookup the property given its name (obtained from the reader) and return it. /// Also sets state.Current.JsonPropertyInfo to a non-null value. /// - // 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() {