File: System\Text\Json\Serialization\Metadata\JsonTypeInfo.Cache.cs
Web Access
Project: src\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>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        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 = cacheBuilder.ToArray();
            }
 
            frame.PropertyRefCacheBuilder = null;
        }
    }
}