| File: System\Text\Json\Serialization\Metadata\JsonTypeInfo.Cache.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// 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; using System.Runtime.CompilerServices; namespace System.Text.Json.Serialization.Metadata { public abstract partial class JsonTypeInfo { /// <summary> /// Cached typeof(object). It is faster to cache this than to call typeof(object) multiple times. /// </summary> internal static readonly Type ObjectType = typeof(object); // 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. internal int ParameterCount { get; private protected set; } // All of the serializable parameters on a POCO constructor internal ReadOnlySpan<JsonParameterInfo> ParameterCache { get { Debug.Assert(IsConfigured && _parameterCache is not null); return _parameterCache; } } internal bool UsesParameterizedConstructor { get { Debug.Assert(IsConfigured); return _parameterCache != null; } } private JsonParameterInfo[]? _parameterCache; // All of the serializable properties on a POCO (minus the extension property). internal ReadOnlySpan<JsonPropertyInfo> PropertyCache { get { Debug.Assert(IsConfigured && _propertyCache is not null); return _propertyCache; } } private JsonPropertyInfo[]? _propertyCache; // All of the serializable properties on a POCO (minus the extension property) keyed on property name. internal Dictionary<string, JsonPropertyInfo> PropertyIndex { get { Debug.Assert(IsConfigured && _propertyIndex is not null); return _propertyIndex; } } private Dictionary<string, JsonPropertyInfo>? _propertyIndex; /// <summary> /// Stores a cache of UTF-8 encoded property names and their associated JsonPropertyInfo, if available. /// 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. /// </summary> private PropertyRef[] _utf8PropertyCache = []; /// <summary> /// Defines the core property lookup logic for a given unescaped UTF-8 encoded property name. /// </summary> internal JsonPropertyInfo? GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName) { Debug.Assert(IsConfigured); // The logic can be broken up into roughly three stages: // 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. PropertyRef[] utf8PropertyCache = _utf8PropertyCache; // Keep a local copy of the cache in case it changes by another thread. ReadOnlySpan<PropertyRef> utf8PropertyCacheSpan = utf8PropertyCache; ulong key = PropertyRef.GetKey(propertyName); if (!utf8PropertyCacheSpan.IsEmpty) { PropertyRef propertyRef; // Start with the current property index, and then go forwards\backwards. int propertyIndex = frame.PropertyIndex; int count = utf8PropertyCacheSpan.Length; int iForward = Math.Min(propertyIndex, count); int iBackward = iForward - 1; while (true) { if (iForward < count) { propertyRef = utf8PropertyCacheSpan[iForward]; if (propertyRef.Equals(propertyName, key)) { utf8PropertyName = propertyRef.Utf8PropertyName; return propertyRef.Info; } ++iForward; if (iBackward >= 0) { propertyRef = utf8PropertyCacheSpan[iBackward]; if (propertyRef.Equals(propertyName, key)) { utf8PropertyName = propertyRef.Utf8PropertyName; return propertyRef.Info; } --iBackward; } } else if (iBackward >= 0) { propertyRef = utf8PropertyCacheSpan[iBackward]; if (propertyRef.Equals(propertyName, key)) { utf8PropertyName = propertyRef.Utf8PropertyName; return propertyRef.Info; } --iBackward; } else { // Property was not found. break; } } } // No cached item was found. Try the main dictionary which has all of the properties. if (PropertyIndex.TryLookupUtf8Key(propertyName, out JsonPropertyInfo? info) && (!Options.PropertyNameCaseInsensitive || propertyName.SequenceEqual(info.NameAsUtf8Bytes))) { // We have an exact match in UTF8 encoding. utf8PropertyName = info.NameAsUtf8Bytes; } else { // Make a copy of the original Span. utf8PropertyName = propertyName.ToArray(); } // Assuming there is capacity, store the new result for potential // inclusion to the UTF-8 cache once deserialization is complete. ref PropertyRefCacheBuilder? cacheBuilder = ref frame.PropertyRefCacheBuilder; if ((cacheBuilder?.TotalCount ?? utf8PropertyCache.Length) < PropertyRefCacheBuilder.MaxCapacity) { (cacheBuilder ??= new(utf8PropertyCache)).TryAdd(new(key, info, utf8PropertyName)); } return info; } /// <summary> /// Attempts to update the UTF-8 property cache with the results gathered in the current deserialization operation. /// The update operation is done optimistically and results are discarded if the cache was updated by another thread. /// </summary> internal void UpdateUtf8PropertyCache(ref ReadStackFrame frame) { Debug.Assert(frame.PropertyRefCacheBuilder is { Count: > 0 }); PropertyRef[]? currentCache = _utf8PropertyCache; PropertyRefCacheBuilder cacheBuilder = frame.PropertyRefCacheBuilder; if (currentCache == cacheBuilder.OriginalCache) { PropertyRef[] newCache = cacheBuilder.ToArray(); Debug.Assert(newCache.Length <= PropertyRefCacheBuilder.MaxCapacity); _utf8PropertyCache = newCache; } frame.PropertyRefCacheBuilder = null; } } }