mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-09 17:44:48 +09:00
Clean up JSON property lookup logic and add alternate key lookup support. (#103836)
* Clean up JSON property lookup logic and add alternate lookup support. * Ensure PropertyRef cache doesn't contain duplicates. * Remove usings. * Revert back to using original caching algorithm. * Incorporate suggestions to key generation algorithm. * Address feedback. * Simplify more PropertyRef methods.
This commit is contained in:
parent
f99194cb7f
commit
e4d9e266b7
11 changed files with 268 additions and 246 deletions
|
@ -158,6 +158,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
|
||||||
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
|
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
|
||||||
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverChain.cs" />
|
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverChain.cs" />
|
||||||
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverWithAddedModifiers.cs" />
|
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverWithAddedModifiers.cs" />
|
||||||
|
<Compile Include="System\Text\Json\Serialization\Metadata\PropertyRefCacheBuilder.cs" />
|
||||||
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
|
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
|
||||||
<Compile Include="System\Text\Json\Writer\Utf8JsonWriterCache.cs" />
|
<Compile Include="System\Text\Json\Writer\Utf8JsonWriterCache.cs" />
|
||||||
<Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
|
<Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Buffers.Text;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -213,6 +214,41 @@ namespace System.Text.Json
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryLookupUtf8Key<TValue>(
|
||||||
|
this Dictionary<string, TValue> dictionary,
|
||||||
|
ReadOnlySpan<byte> utf8Key,
|
||||||
|
[MaybeNullWhen(false)] out TValue result)
|
||||||
|
{
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
Debug.Assert(dictionary.Comparer is IAlternateEqualityComparer<ReadOnlySpan<char>, string>);
|
||||||
|
|
||||||
|
Dictionary<string, TValue>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
|
||||||
|
dictionary.GetAlternateLookup<string, TValue, ReadOnlySpan<char>>();
|
||||||
|
|
||||||
|
char[]? rentedBuffer = null;
|
||||||
|
|
||||||
|
Span<char> charBuffer = utf8Key.Length <= JsonConstants.StackallocCharThreshold ?
|
||||||
|
stackalloc char[JsonConstants.StackallocCharThreshold] :
|
||||||
|
(rentedBuffer = ArrayPool<char>.Shared.Rent(utf8Key.Length));
|
||||||
|
|
||||||
|
int charsWritten = Encoding.UTF8.GetChars(utf8Key, charBuffer);
|
||||||
|
Span<char> decodedKey = charBuffer[0..charsWritten];
|
||||||
|
|
||||||
|
bool success = spanLookup.TryGetValue(decodedKey, out result);
|
||||||
|
|
||||||
|
if (rentedBuffer != null)
|
||||||
|
{
|
||||||
|
decodedKey.Clear();
|
||||||
|
ArrayPool<char>.Shared.Return(rentedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
#else
|
||||||
|
string key = Utf8GetString(utf8Key);
|
||||||
|
return dictionary.TryGetValue(key, out result);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emulates Dictionary(IEnumerable{KeyValuePair}) on netstandard.
|
/// Emulates Dictionary(IEnumerable{KeyValuePair}) on netstandard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -171,6 +171,7 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
// Read method would have thrown if otherwise.
|
// Read method would have thrown if otherwise.
|
||||||
Debug.Assert(tokenType == JsonTokenType.PropertyName);
|
Debug.Assert(tokenType == JsonTokenType.PropertyName);
|
||||||
|
|
||||||
|
jsonTypeInfo.ValidateCanBeUsedForPropertyMetadataSerialization();
|
||||||
ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options, out bool isAlreadyReadMetadataProperty);
|
ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options, out bool isAlreadyReadMetadataProperty);
|
||||||
if (isAlreadyReadMetadataProperty)
|
if (isAlreadyReadMetadataProperty)
|
||||||
{
|
{
|
||||||
|
@ -185,7 +186,6 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
unescapedPropertyName,
|
unescapedPropertyName,
|
||||||
ref state,
|
ref state,
|
||||||
options,
|
options,
|
||||||
out byte[] _,
|
|
||||||
out bool useExtensionProperty);
|
out bool useExtensionProperty);
|
||||||
|
|
||||||
state.Current.UseExtensionProperty = useExtensionProperty;
|
state.Current.UseExtensionProperty = useExtensionProperty;
|
||||||
|
@ -257,10 +257,10 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
Debug.Assert(obj != null);
|
Debug.Assert(obj != null);
|
||||||
value = (T)obj;
|
value = (T)obj;
|
||||||
|
|
||||||
// Check if we are trying to build the sorted cache.
|
// Check if we are trying to update the UTF-8 property cache.
|
||||||
if (state.Current.PropertyRefCache != null)
|
if (state.Current.PropertyRefCacheBuilder != null)
|
||||||
{
|
{
|
||||||
jsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
|
jsonTypeInfo.UpdateUtf8PropertyCache(ref state.Current);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -292,12 +292,12 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options, out bool isAlreadyReadMetadataProperty);
|
ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options, out bool isAlreadyReadMetadataProperty);
|
||||||
Debug.Assert(!isAlreadyReadMetadataProperty, "Only possible for types that can read metadata, which do not call into the fast-path method.");
|
Debug.Assert(!isAlreadyReadMetadataProperty, "Only possible for types that can read metadata, which do not call into the fast-path method.");
|
||||||
|
|
||||||
|
jsonTypeInfo.ValidateCanBeUsedForPropertyMetadataSerialization();
|
||||||
JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
|
JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
|
||||||
obj,
|
obj,
|
||||||
unescapedPropertyName,
|
unescapedPropertyName,
|
||||||
ref state,
|
ref state,
|
||||||
options,
|
options,
|
||||||
out byte[] _,
|
|
||||||
out bool useExtensionProperty);
|
out bool useExtensionProperty);
|
||||||
|
|
||||||
ReadPropertyValue(obj, ref state, ref reader, jsonPropertyInfo, useExtensionProperty);
|
ReadPropertyValue(obj, ref state, ref reader, jsonPropertyInfo, useExtensionProperty);
|
||||||
|
@ -306,10 +306,10 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
jsonTypeInfo.OnDeserialized?.Invoke(obj);
|
jsonTypeInfo.OnDeserialized?.Invoke(obj);
|
||||||
state.Current.ValidateAllRequiredPropertiesAreRead(jsonTypeInfo);
|
state.Current.ValidateAllRequiredPropertiesAreRead(jsonTypeInfo);
|
||||||
|
|
||||||
// Check if we are trying to build the sorted cache.
|
// Check if we are trying to update the UTF-8 property cache.
|
||||||
if (state.Current.PropertyRefCache != null)
|
if (state.Current.PropertyRefCacheBuilder != null)
|
||||||
{
|
{
|
||||||
jsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
|
jsonTypeInfo.UpdateUtf8PropertyCache(ref state.Current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -255,10 +255,10 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
Debug.Assert(obj != null);
|
Debug.Assert(obj != null);
|
||||||
value = (T)obj;
|
value = (T)obj;
|
||||||
|
|
||||||
// Check if we are trying to build the sorted cache.
|
// Check if we are trying to update the UTF-8 property cache.
|
||||||
if (state.Current.PropertyRefCache != null)
|
if (state.Current.PropertyRefCacheBuilder != null)
|
||||||
{
|
{
|
||||||
state.Current.JsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
|
jsonTypeInfo.UpdateUtf8PropertyCache(ref state.Current);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -603,13 +603,9 @@ namespace System.Text.Json.Serialization.Converters
|
||||||
unescapedPropertyName,
|
unescapedPropertyName,
|
||||||
ref state,
|
ref state,
|
||||||
options,
|
options,
|
||||||
out byte[] utf8PropertyName,
|
|
||||||
out bool useExtensionProperty,
|
out bool useExtensionProperty,
|
||||||
createExtensionProperty: false);
|
createExtensionProperty: false);
|
||||||
|
|
||||||
// For case insensitive and missing property support of JsonPath, remember the value on the temporary stack.
|
|
||||||
state.Current.JsonPropertyName = utf8PropertyName;
|
|
||||||
|
|
||||||
jsonParameterInfo = jsonPropertyInfo.AssociatedParameter;
|
jsonParameterInfo = jsonPropertyInfo.AssociatedParameter;
|
||||||
if (jsonParameterInfo != null)
|
if (jsonParameterInfo != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,17 +21,16 @@ namespace System.Text.Json
|
||||||
ReadOnlySpan<byte> unescapedPropertyName,
|
ReadOnlySpan<byte> unescapedPropertyName,
|
||||||
ref ReadStack state,
|
ref ReadStack state,
|
||||||
JsonSerializerOptions options,
|
JsonSerializerOptions options,
|
||||||
out byte[] utf8PropertyName,
|
|
||||||
out bool useExtensionProperty,
|
out bool useExtensionProperty,
|
||||||
bool createExtensionProperty = true)
|
bool createExtensionProperty = true)
|
||||||
{
|
{
|
||||||
JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
|
JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
|
||||||
useExtensionProperty = false;
|
useExtensionProperty = false;
|
||||||
|
|
||||||
JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.GetProperty(
|
JsonPropertyInfo? jsonPropertyInfo = jsonTypeInfo.GetProperty(
|
||||||
unescapedPropertyName,
|
unescapedPropertyName,
|
||||||
ref state.Current,
|
ref state.Current,
|
||||||
out utf8PropertyName);
|
out byte[] utf8PropertyName);
|
||||||
|
|
||||||
// Increment PropertyIndex so GetProperty() checks the next property first when called again.
|
// Increment PropertyIndex so GetProperty() checks the next property first when called again.
|
||||||
state.Current.PropertyIndex++;
|
state.Current.PropertyIndex++;
|
||||||
|
@ -40,7 +39,7 @@ namespace System.Text.Json
|
||||||
state.Current.JsonPropertyName = utf8PropertyName;
|
state.Current.JsonPropertyName = utf8PropertyName;
|
||||||
|
|
||||||
// Handle missing properties
|
// Handle missing properties
|
||||||
if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
|
if (jsonPropertyInfo is null)
|
||||||
{
|
{
|
||||||
if (jsonTypeInfo.EffectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
|
if (jsonTypeInfo.EffectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
|
||||||
{
|
{
|
||||||
|
@ -63,6 +62,11 @@ namespace System.Text.Json
|
||||||
jsonPropertyInfo = dataExtProperty;
|
jsonPropertyInfo = dataExtProperty;
|
||||||
useExtensionProperty = true;
|
useExtensionProperty = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Populate with a placeholder value required by JsonPath calculations
|
||||||
|
jsonPropertyInfo = JsonPropertyInfo.s_missingProperty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Current.JsonPropertyInfo = jsonPropertyInfo;
|
state.Current.JsonPropertyInfo = jsonPropertyInfo;
|
||||||
|
|
|
@ -800,12 +800,12 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utf8 version of Name.
|
/// Utf8 version of Name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal byte[] NameAsUtf8Bytes { get; set; } = null!;
|
internal byte[] NameAsUtf8Bytes { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The escaped name passed to the writer.
|
/// The escaped name passed to the writer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal byte[] EscapedNameSection { get; set; } = null!;
|
internal byte[] EscapedNameSection { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="JsonSerializerOptions"/> value associated with the current contract instance.
|
/// Gets the <see cref="JsonSerializerOptions"/> value associated with the current contract instance.
|
||||||
|
|
|
@ -3,10 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.Json.Reflection;
|
|
||||||
|
|
||||||
namespace System.Text.Json.Serialization.Metadata
|
namespace System.Text.Json.Serialization.Metadata
|
||||||
{
|
{
|
||||||
|
@ -17,14 +14,6 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly Type ObjectType = typeof(object);
|
internal 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.
|
|
||||||
private const int PropertyNameKeyLength = 7;
|
|
||||||
|
|
||||||
// The limit to how many property names from the JSON are cached in _propertyRefsSorted before using PropertyCache.
|
|
||||||
private const int PropertyNameCountCacheThreshold = 64;
|
|
||||||
|
|
||||||
// The number of parameters the deserialization constructor has. If this is not equal to ParameterCache.Count, this means
|
// The number of parameters the deserialization constructor has. If this is not equal to ParameterCache.Count, this means
|
||||||
// that not all parameters are bound to object properties, and an exception will be thrown if deserialization is attempted.
|
// that not all parameters are bound to object properties, and an exception will be thrown if deserialization is attempted.
|
||||||
internal int ParameterCount { get; private protected set; }
|
internal int ParameterCount { get; private protected set; }
|
||||||
|
@ -74,64 +63,38 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
|
|
||||||
private Dictionary<string, JsonPropertyInfo>? _propertyIndex;
|
private Dictionary<string, JsonPropertyInfo>? _propertyIndex;
|
||||||
|
|
||||||
// Fast cache of properties by first JSON ordering; may not contain all properties. Accessed before PropertyCache.
|
/// <summary>
|
||||||
// Use an array (instead of List<T>) for highest performance.
|
/// Stores a cache of UTF-8 encoded property names and their associated JsonPropertyInfo, if available.
|
||||||
private volatile PropertyRef[]? _propertyRefsSorted;
|
/// Consulted before the <see cref="PropertyIndex" /> lookup to avoid added allocations and decoding costs.
|
||||||
|
/// The cache is grown on-demand appending encountered unbounded properties or alternative casings.
|
||||||
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
|
/// </summary>
|
||||||
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
|
private PropertyRef[] _utf8PropertyCache = [];
|
||||||
internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType, Type? declaringType)
|
|
||||||
{
|
|
||||||
JsonPropertyInfo jsonPropertyInfo;
|
|
||||||
|
|
||||||
if (Options.TryGetTypeInfoCached(propertyType, out JsonTypeInfo? jsonTypeInfo))
|
|
||||||
{
|
|
||||||
// If a JsonTypeInfo has already been cached for the property type,
|
|
||||||
// avoid reflection-based initialization by delegating construction
|
|
||||||
// of JsonPropertyInfo<T> construction to the property type metadata.
|
|
||||||
jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(declaringTypeInfo: this, declaringType, Options);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Metadata for `propertyType` has not been registered yet.
|
|
||||||
// Use reflection to instantiate the correct JsonPropertyInfo<T>
|
|
||||||
Type propertyInfoType = typeof(JsonPropertyInfo<>).MakeGenericType(propertyType);
|
|
||||||
jsonPropertyInfo = (JsonPropertyInfo)propertyInfoType.CreateInstanceNoWrapExceptions(
|
|
||||||
parameterTypes: new Type[] { typeof(Type), typeof(JsonTypeInfo), typeof(JsonSerializerOptions) },
|
|
||||||
parameters: new object[] { declaringType ?? Type, this, Options })!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(jsonPropertyInfo.PropertyType == propertyType);
|
|
||||||
return jsonPropertyInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a JsonPropertyInfo whose property type matches the type of this JsonTypeInfo instance.
|
/// Defines the core property lookup logic for a given unescaped UTF-8 encoded property name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private protected abstract JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, Type? declaringType, JsonSerializerOptions options);
|
|
||||||
|
|
||||||
// AggressiveInlining used although a large method it is only called from one location and is on a hot path.
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal JsonPropertyInfo GetProperty(
|
internal JsonPropertyInfo? GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName)
|
||||||
ReadOnlySpan<byte> propertyName,
|
|
||||||
ref ReadStackFrame frame,
|
|
||||||
out byte[] utf8PropertyName)
|
|
||||||
{
|
{
|
||||||
PropertyRef propertyRef;
|
Debug.Assert(IsConfigured);
|
||||||
|
|
||||||
ValidateCanBeUsedForPropertyMetadataSerialization();
|
// The logic can be broken up into roughly three stages:
|
||||||
ulong key = GetKey(propertyName);
|
// 1. Look up the UTF-8 property cache for potential exact matches in the encoding.
|
||||||
|
// 2. If no match is found, decode to UTF-16 and look up the primary dictionary.
|
||||||
|
// 3. Store the new result for potential inclusion to the UTF-8 cache once deserialization is complete.
|
||||||
|
|
||||||
// Keep a local copy of the cache in case it changes by another thread.
|
PropertyRef[] utf8PropertyCache = _utf8PropertyCache; // Keep a local copy of the cache in case it changes by another thread.
|
||||||
PropertyRef[]? localPropertyRefsSorted = _propertyRefsSorted;
|
ReadOnlySpan<PropertyRef> utf8PropertyCacheSpan = utf8PropertyCache;
|
||||||
|
ulong key = PropertyRef.GetKey(propertyName);
|
||||||
|
|
||||||
// If there is an existing cache, then use it.
|
if (!utf8PropertyCacheSpan.IsEmpty)
|
||||||
if (localPropertyRefsSorted != null)
|
|
||||||
{
|
{
|
||||||
|
PropertyRef propertyRef;
|
||||||
|
|
||||||
// Start with the current property index, and then go forwards\backwards.
|
// Start with the current property index, and then go forwards\backwards.
|
||||||
int propertyIndex = frame.PropertyIndex;
|
int propertyIndex = frame.PropertyIndex;
|
||||||
|
|
||||||
int count = localPropertyRefsSorted.Length;
|
int count = utf8PropertyCacheSpan.Length;
|
||||||
int iForward = Math.Min(propertyIndex, count);
|
int iForward = Math.Min(propertyIndex, count);
|
||||||
int iBackward = iForward - 1;
|
int iBackward = iForward - 1;
|
||||||
|
|
||||||
|
@ -139,10 +102,10 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
{
|
{
|
||||||
if (iForward < count)
|
if (iForward < count)
|
||||||
{
|
{
|
||||||
propertyRef = localPropertyRefsSorted[iForward];
|
propertyRef = utf8PropertyCacheSpan[iForward];
|
||||||
if (IsPropertyRefEqual(propertyRef, propertyName, key))
|
if (propertyRef.Equals(propertyName, key))
|
||||||
{
|
{
|
||||||
utf8PropertyName = propertyRef.NameFromJson;
|
utf8PropertyName = propertyRef.Utf8PropertyName;
|
||||||
return propertyRef.Info;
|
return propertyRef.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,10 +113,10 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
|
|
||||||
if (iBackward >= 0)
|
if (iBackward >= 0)
|
||||||
{
|
{
|
||||||
propertyRef = localPropertyRefsSorted[iBackward];
|
propertyRef = utf8PropertyCacheSpan[iBackward];
|
||||||
if (IsPropertyRefEqual(propertyRef, propertyName, key))
|
if (propertyRef.Equals(propertyName, key))
|
||||||
{
|
{
|
||||||
utf8PropertyName = propertyRef.NameFromJson;
|
utf8PropertyName = propertyRef.Utf8PropertyName;
|
||||||
return propertyRef.Info;
|
return propertyRef.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,10 +125,10 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
}
|
}
|
||||||
else if (iBackward >= 0)
|
else if (iBackward >= 0)
|
||||||
{
|
{
|
||||||
propertyRef = localPropertyRefsSorted[iBackward];
|
propertyRef = utf8PropertyCacheSpan[iBackward];
|
||||||
if (IsPropertyRefEqual(propertyRef, propertyName, key))
|
if (propertyRef.Equals(propertyName, key))
|
||||||
{
|
{
|
||||||
utf8PropertyName = propertyRef.NameFromJson;
|
utf8PropertyName = propertyRef.Utf8PropertyName;
|
||||||
return propertyRef.Info;
|
return propertyRef.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,176 +143,49 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cached item was found. Try the main dictionary which has all of the properties.
|
// No cached item was found. Try the main dictionary which has all of the properties.
|
||||||
if (PropertyIndex.TryGetValue(JsonHelpers.Utf8GetString(propertyName), out JsonPropertyInfo? info))
|
if (PropertyIndex.TryLookupUtf8Key(propertyName, out JsonPropertyInfo? info) &&
|
||||||
|
(!Options.PropertyNameCaseInsensitive || propertyName.SequenceEqual(info.NameAsUtf8Bytes)))
|
||||||
{
|
{
|
||||||
Debug.Assert(info != null, "PropertyCache contains null JsonPropertyInfo");
|
// We have an exact match in UTF8 encoding.
|
||||||
|
utf8PropertyName = info.NameAsUtf8Bytes;
|
||||||
if (Options.PropertyNameCaseInsensitive)
|
|
||||||
{
|
|
||||||
if (propertyName.SequenceEqual(info.NameAsUtf8Bytes))
|
|
||||||
{
|
|
||||||
// Use the existing byte[] reference instead of creating another one.
|
|
||||||
utf8PropertyName = info.NameAsUtf8Bytes!;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Make a copy of the original Span.
|
|
||||||
utf8PropertyName = propertyName.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
utf8PropertyName = info.NameAsUtf8Bytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info = JsonPropertyInfo.s_missingProperty;
|
|
||||||
|
|
||||||
// Make a copy of the original Span.
|
// Make a copy of the original Span.
|
||||||
utf8PropertyName = propertyName.ToArray();
|
utf8PropertyName = propertyName.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we should add this to the cache.
|
// Assuming there is capacity, store the new result for potential
|
||||||
// Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
|
// inclusion to the UTF-8 cache once deserialization is complete.
|
||||||
int cacheCount = 0;
|
|
||||||
if (localPropertyRefsSorted != null)
|
ref PropertyRefCacheBuilder? cacheBuilder = ref frame.PropertyRefCacheBuilder;
|
||||||
|
if ((cacheBuilder?.TotalCount ?? utf8PropertyCache.Length) < PropertyRefCacheBuilder.MaxCapacity)
|
||||||
{
|
{
|
||||||
cacheCount = localPropertyRefsSorted.Length;
|
(cacheBuilder ??= new(utf8PropertyCache)).TryAdd(new(key, info, utf8PropertyName));
|
||||||
}
|
|
||||||
|
|
||||||
// Do a quick check for the stable (after warm-up) case.
|
|
||||||
if (cacheCount < PropertyNameCountCacheThreshold)
|
|
||||||
{
|
|
||||||
// Do a slower check for the warm-up case.
|
|
||||||
if (frame.PropertyRefCache != null)
|
|
||||||
{
|
|
||||||
cacheCount += frame.PropertyRefCache.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check again to append the cache up to the threshold.
|
|
||||||
if (cacheCount < PropertyNameCountCacheThreshold)
|
|
||||||
{
|
|
||||||
frame.PropertyRefCache ??= new List<PropertyRef>();
|
|
||||||
|
|
||||||
Debug.Assert(info != null);
|
|
||||||
|
|
||||||
propertyRef = new PropertyRef(key, info, utf8PropertyName);
|
|
||||||
frame.PropertyRefCache.Add(propertyRef);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool IsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key)
|
|
||||||
{
|
|
||||||
if (key == propertyRef.Key)
|
|
||||||
{
|
|
||||||
// We compare the whole name, although we could skip the first 7 bytes (but it's not any faster)
|
|
||||||
if (propertyName.Length <= PropertyNameKeyLength ||
|
|
||||||
propertyName.SequenceEqual(propertyRef.NameFromJson))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a key from the property name.
|
/// Attempts to update the UTF-8 property cache with the results gathered in the current deserialization operation.
|
||||||
/// The key consists of the first 7 bytes of the property name and then the length.
|
/// The update operation is done optimistically and results are discarded if the cache was updated by another thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// AggressiveInlining used since this method is only called from two locations and is on a hot path.
|
internal void UpdateUtf8PropertyCache(ref ReadStackFrame frame)
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal static ulong GetKey(ReadOnlySpan<byte> name)
|
|
||||||
{
|
{
|
||||||
ulong key;
|
Debug.Assert(frame.PropertyRefCacheBuilder is { Count: > 0 });
|
||||||
|
|
||||||
ref byte reference = ref MemoryMarshal.GetReference(name);
|
PropertyRef[]? currentCache = _utf8PropertyCache;
|
||||||
int length = name.Length;
|
PropertyRefCacheBuilder cacheBuilder = frame.PropertyRefCacheBuilder;
|
||||||
|
|
||||||
if (length > 7)
|
if (currentCache == cacheBuilder.OriginalCache)
|
||||||
{
|
{
|
||||||
key = Unsafe.ReadUnaligned<ulong>(ref reference) & 0x00ffffffffffffffL;
|
PropertyRef[] newCache = cacheBuilder.ToArray();
|
||||||
key |= (ulong)Math.Min(length, 0xff) << 56;
|
Debug.Assert(newCache.Length <= PropertyRefCacheBuilder.MaxCapacity);
|
||||||
}
|
_utf8PropertyCache = cacheBuilder.ToArray();
|
||||||
else
|
|
||||||
{
|
|
||||||
key =
|
|
||||||
length > 5 ? Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32 :
|
|
||||||
length > 3 ? Unsafe.ReadUnaligned<uint>(ref reference) :
|
|
||||||
length > 1 ? Unsafe.ReadUnaligned<ushort>(ref reference) : 0UL;
|
|
||||||
key |= (ulong)length << 56;
|
|
||||||
|
|
||||||
if ((length & 1) != 0)
|
|
||||||
{
|
|
||||||
var offset = length - 1;
|
|
||||||
key |= (ulong)Unsafe.Add(ref reference, offset) << (offset * 8);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
frame.PropertyRefCacheBuilder = null;
|
||||||
// Verify key contains the embedded bytes as expected.
|
|
||||||
// Note: the expected properties do not hold true on big-endian platforms
|
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
{
|
|
||||||
const int BitsInByte = 8;
|
|
||||||
Debug.Assert(
|
|
||||||
// Verify embedded property name.
|
|
||||||
(name.Length < 1 || name[0] == ((key & ((ulong)0xFF << BitsInByte * 0)) >> BitsInByte * 0)) &&
|
|
||||||
(name.Length < 2 || name[1] == ((key & ((ulong)0xFF << BitsInByte * 1)) >> BitsInByte * 1)) &&
|
|
||||||
(name.Length < 3 || name[2] == ((key & ((ulong)0xFF << BitsInByte * 2)) >> BitsInByte * 2)) &&
|
|
||||||
(name.Length < 4 || name[3] == ((key & ((ulong)0xFF << BitsInByte * 3)) >> BitsInByte * 3)) &&
|
|
||||||
(name.Length < 5 || name[4] == ((key & ((ulong)0xFF << BitsInByte * 4)) >> BitsInByte * 4)) &&
|
|
||||||
(name.Length < 6 || name[5] == ((key & ((ulong)0xFF << BitsInByte * 5)) >> BitsInByte * 5)) &&
|
|
||||||
(name.Length < 7 || name[6] == ((key & ((ulong)0xFF << BitsInByte * 6)) >> BitsInByte * 6)) &&
|
|
||||||
// Verify embedded length.
|
|
||||||
(name.Length >= 0xFF || (key & ((ulong)0xFF << BitsInByte * 7)) >> BitsInByte * 7 == (ulong)name.Length) &&
|
|
||||||
(name.Length < 0xFF || (key & ((ulong)0xFF << BitsInByte * 7)) >> BitsInByte * 7 == 0xFF),
|
|
||||||
"Embedded bytes not as expected");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateSortedPropertyCache(ref ReadStackFrame frame)
|
|
||||||
{
|
|
||||||
Debug.Assert(frame.PropertyRefCache != null);
|
|
||||||
|
|
||||||
// frame.PropertyRefCache is only read\written by a single thread -- the thread performing
|
|
||||||
// the deserialization for a given object instance.
|
|
||||||
|
|
||||||
List<PropertyRef> listToAppend = frame.PropertyRefCache;
|
|
||||||
|
|
||||||
// _propertyRefsSorted can be accessed by multiple threads, so replace the reference when
|
|
||||||
// appending to it. No lock() is necessary.
|
|
||||||
|
|
||||||
if (_propertyRefsSorted != null)
|
|
||||||
{
|
|
||||||
List<PropertyRef> replacementList = new List<PropertyRef>(_propertyRefsSorted);
|
|
||||||
Debug.Assert(replacementList.Count <= PropertyNameCountCacheThreshold);
|
|
||||||
|
|
||||||
// Verify replacementList will not become too large.
|
|
||||||
while (replacementList.Count + listToAppend.Count > PropertyNameCountCacheThreshold)
|
|
||||||
{
|
|
||||||
// This code path is rare; keep it simple by using RemoveAt() instead of RemoveRange() which requires calculating index\count.
|
|
||||||
listToAppend.RemoveAt(listToAppend.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new items; duplicates are possible but that is tolerated during property lookup.
|
|
||||||
replacementList.AddRange(listToAppend);
|
|
||||||
_propertyRefsSorted = replacementList.ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_propertyRefsSorted = listToAppend.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.PropertyRefCache = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -966,7 +966,7 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
{
|
{
|
||||||
Type jsonTypeInfoType = typeof(JsonTypeInfo<>).MakeGenericType(type);
|
Type jsonTypeInfoType = typeof(JsonTypeInfo<>).MakeGenericType(type);
|
||||||
jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
|
jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
|
||||||
parameterTypes: new Type[] { typeof(JsonConverter), typeof(JsonSerializerOptions) },
|
parameterTypes: [typeof(JsonConverter), typeof(JsonSerializerOptions)],
|
||||||
parameters: new object[] { converter, options })!;
|
parameters: new object[] { converter, options })!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1009,6 +1009,38 @@ namespace System.Text.Json.Serialization.Metadata
|
||||||
return propertyInfo;
|
return propertyInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
|
||||||
|
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
|
||||||
|
internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType, Type? declaringType)
|
||||||
|
{
|
||||||
|
JsonPropertyInfo jsonPropertyInfo;
|
||||||
|
|
||||||
|
if (Options.TryGetTypeInfoCached(propertyType, out JsonTypeInfo? jsonTypeInfo))
|
||||||
|
{
|
||||||
|
// If a JsonTypeInfo has already been cached for the property type,
|
||||||
|
// avoid reflection-based initialization by delegating construction
|
||||||
|
// of JsonPropertyInfo<T> construction to the property type metadata.
|
||||||
|
jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(declaringTypeInfo: this, declaringType, Options);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Metadata for `propertyType` has not been registered yet.
|
||||||
|
// Use reflection to instantiate the correct JsonPropertyInfo<T>
|
||||||
|
Type propertyInfoType = typeof(JsonPropertyInfo<>).MakeGenericType(propertyType);
|
||||||
|
jsonPropertyInfo = (JsonPropertyInfo)propertyInfoType.CreateInstanceNoWrapExceptions(
|
||||||
|
parameterTypes: [typeof(Type), typeof(JsonTypeInfo), typeof(JsonSerializerOptions)],
|
||||||
|
parameters: new object[] { declaringType ?? Type, this, Options })!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(jsonPropertyInfo.PropertyType == propertyType);
|
||||||
|
return jsonPropertyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a JsonPropertyInfo whose property type matches the type of this JsonTypeInfo instance.
|
||||||
|
/// </summary>
|
||||||
|
private protected abstract JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, Type? declaringType, JsonSerializerOptions options);
|
||||||
|
|
||||||
private protected Dictionary<ParameterLookupKey, JsonParameterInfoValues>? _parameterInfoValuesIndex;
|
private protected Dictionary<ParameterLookupKey, JsonParameterInfoValues>? _parameterInfoValuesIndex;
|
||||||
|
|
||||||
// Untyped, root-level serialization methods
|
// Untyped, root-level serialization methods
|
||||||
|
|
|
@ -1,21 +1,100 @@
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace System.Text.Json.Serialization.Metadata
|
namespace System.Text.Json.Serialization.Metadata
|
||||||
{
|
{
|
||||||
internal readonly struct PropertyRef
|
/// <summary>
|
||||||
|
/// Represents a UTF-8 encoded JSON property name and its associated <see cref="JsonPropertyInfo"/>, if available.
|
||||||
|
/// PropertyRefs use byte sequence equality, so equal JSON strings with alternate encodings or casings are not equal.
|
||||||
|
/// Used as a first-level cache for property lookups before falling back to UTF decoding and string comparison.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly struct PropertyRef(ulong key, JsonPropertyInfo? info, byte[] utf8PropertyName) : IEquatable<PropertyRef>
|
||||||
{
|
{
|
||||||
public PropertyRef(ulong key, JsonPropertyInfo info, byte[] nameFromJson)
|
// 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.
|
||||||
|
private const int PropertyNameKeyLength = 7;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A custom hashcode produced from the UTF-8 encoded property name.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong Key = key;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="JsonPropertyInfo"/> associated with the property name, if available.
|
||||||
|
/// </summary>
|
||||||
|
public readonly JsonPropertyInfo? Info = info;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a heap allocated copy of the UTF-8 encoded property name.
|
||||||
|
/// </summary>
|
||||||
|
public readonly byte[] Utf8PropertyName = utf8PropertyName;
|
||||||
|
|
||||||
|
public bool Equals(PropertyRef other) => Equals(other.Utf8PropertyName, other.Key);
|
||||||
|
public override bool Equals(object? obj) => obj is PropertyRef other && Equals(other);
|
||||||
|
public override int GetHashCode() => Key.GetHashCode();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool Equals(ReadOnlySpan<byte> propertyName, ulong key)
|
||||||
{
|
{
|
||||||
Key = key;
|
// If the property name is less than 8 bytes, it is embedded in the key so no further comparison is necessary.
|
||||||
Info = info;
|
return key == Key && (propertyName.Length <= PropertyNameKeyLength || propertyName.SequenceEqual(Utf8PropertyName));
|
||||||
NameFromJson = nameFromJson;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly ulong Key;
|
/// <summary>
|
||||||
public readonly JsonPropertyInfo Info;
|
/// Get a key from the property name.
|
||||||
|
/// The key consists of the first 7 bytes of the property name and then the least significant bits of the length.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong GetKey(ReadOnlySpan<byte> name)
|
||||||
|
{
|
||||||
|
ref byte reference = ref MemoryMarshal.GetReference(name);
|
||||||
|
int length = name.Length;
|
||||||
|
ulong key = (ulong)(byte)length << 56;
|
||||||
|
|
||||||
// NameFromJson may be different than Info.NameAsUtf8Bytes when case insensitive is enabled.
|
switch (length)
|
||||||
public readonly byte[] NameFromJson;
|
{
|
||||||
|
case 0: goto ComputedKey;
|
||||||
|
case 1: goto OddLength;
|
||||||
|
case 2: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto ComputedKey;
|
||||||
|
case 3: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto OddLength;
|
||||||
|
case 4: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto ComputedKey;
|
||||||
|
case 5: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto OddLength;
|
||||||
|
case 6: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto ComputedKey;
|
||||||
|
case 7: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto OddLength;
|
||||||
|
default: key |= Unsafe.ReadUnaligned<ulong>(ref reference) & 0x00ffffffffffffffL; goto ComputedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
OddLength:
|
||||||
|
int offset = length - 1;
|
||||||
|
key |= (ulong)Unsafe.Add(ref reference, offset) << (offset * 8);
|
||||||
|
|
||||||
|
ComputedKey:
|
||||||
|
#if DEBUG
|
||||||
|
// Verify key contains the embedded bytes as expected.
|
||||||
|
// Note: the expected properties do not hold true on big-endian platforms
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
const int BitsInByte = 8;
|
||||||
|
Debug.Assert(
|
||||||
|
// Verify embedded property name.
|
||||||
|
(name.Length < 1 || name[0] == ((key & ((ulong)0xFF << BitsInByte * 0)) >> BitsInByte * 0)) &&
|
||||||
|
(name.Length < 2 || name[1] == ((key & ((ulong)0xFF << BitsInByte * 1)) >> BitsInByte * 1)) &&
|
||||||
|
(name.Length < 3 || name[2] == ((key & ((ulong)0xFF << BitsInByte * 2)) >> BitsInByte * 2)) &&
|
||||||
|
(name.Length < 4 || name[3] == ((key & ((ulong)0xFF << BitsInByte * 3)) >> BitsInByte * 3)) &&
|
||||||
|
(name.Length < 5 || name[4] == ((key & ((ulong)0xFF << BitsInByte * 4)) >> BitsInByte * 4)) &&
|
||||||
|
(name.Length < 6 || name[5] == ((key & ((ulong)0xFF << BitsInByte * 5)) >> BitsInByte * 5)) &&
|
||||||
|
(name.Length < 7 || name[6] == ((key & ((ulong)0xFF << BitsInByte * 6)) >> BitsInByte * 6)) &&
|
||||||
|
// Verify embedded length.
|
||||||
|
(key & ((ulong)0xFF << BitsInByte * 7)) >> BitsInByte * 7 == (byte)name.Length,
|
||||||
|
"Embedded bytes not as expected");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace System.Text.Json.Serialization.Metadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines builder type for constructing updated <see cref="PropertyRef"/> caches.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class PropertyRefCacheBuilder(PropertyRef[] originalCache)
|
||||||
|
{
|
||||||
|
public const int MaxCapacity = 64;
|
||||||
|
private readonly List<PropertyRef> _propertyRefs = [];
|
||||||
|
private readonly HashSet<PropertyRef> _added = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores a reference to the original cache off which the current list is being built.
|
||||||
|
/// </summary>
|
||||||
|
public readonly PropertyRef[] OriginalCache = originalCache;
|
||||||
|
public int Count => _propertyRefs.Count;
|
||||||
|
public int TotalCount => OriginalCache.Length + _propertyRefs.Count;
|
||||||
|
public PropertyRef[] ToArray() => [.. OriginalCache, .. _propertyRefs];
|
||||||
|
|
||||||
|
public void TryAdd(PropertyRef propertyRef)
|
||||||
|
{
|
||||||
|
Debug.Assert(TotalCount < MaxCapacity, "Should have been checked by the caller.");
|
||||||
|
|
||||||
|
if (_added.Add(propertyRef))
|
||||||
|
{
|
||||||
|
_propertyRefs.Add(propertyRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,7 +65,9 @@ namespace System.Text.Json
|
||||||
|
|
||||||
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
|
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
|
||||||
public int PropertyIndex;
|
public int PropertyIndex;
|
||||||
public List<PropertyRef>? PropertyRefCache;
|
|
||||||
|
// Tracks newly encounentered UTF-8 encoded properties during the current deserialization, to be appended to the cache.
|
||||||
|
public PropertyRefCacheBuilder? PropertyRefCacheBuilder;
|
||||||
|
|
||||||
// Holds relevant state when deserializing objects with parameterized constructors.
|
// Holds relevant state when deserializing objects with parameterized constructors.
|
||||||
public ArgumentState? CtorArgumentState;
|
public ArgumentState? CtorArgumentState;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue