File: System\Text\Json\Serialization\ReadStackFrame.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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
 
namespace System.Text.Json
{
    [StructLayout(LayoutKind.Auto)]
    [DebuggerDisplay("{DebuggerDisplay,nq}")]
    internal struct ReadStackFrame
    {
        // Current property values.
        public JsonPropertyInfo? JsonPropertyInfo;
        public StackFramePropertyState PropertyState;
        public bool UseExtensionProperty;
 
        // Support JSON Path on exceptions and non-string Dictionary keys.
        // This is Utf8 since we don't want to convert to string until an exception is thrown.
        // For dictionary keys we don't want to convert to TKey until we have both key and value when parsing the dictionary elements on stream cases.
        public byte[]? JsonPropertyName;
        public string? JsonPropertyNameAsString; // This is used for string dictionary keys and re-entry cases that specify a property name.
 
        // Stores the non-string dictionary keys for continuation.
        public object? DictionaryKey;
 
        /// <summary>
        /// Records the Utf8JsonReader Depth at the start of the current value.
        /// </summary>
        public int OriginalDepth;
#if DEBUG
        /// <summary>
        /// Records the Utf8JsonReader TokenType at the start of the current value.
        /// Only used to validate debug builds.
        /// </summary>
        public JsonTokenType OriginalTokenType;
#endif
 
        // Current object (POCO or IEnumerable).
        public object? ReturnValue; // The current return value used for re-entry.
        public JsonTypeInfo JsonTypeInfo;
        public StackFrameObjectState ObjectState; // State tracking the current object.
 
        // Current object can contain metadata
        public bool CanContainMetadata;
        public MetadataPropertyName LatestMetadataPropertyName;
        public MetadataPropertyName MetadataPropertyNames;
 
        // Serialization state for value serialized by the current frame.
        public PolymorphicSerializationState PolymorphicSerializationState;
 
        // Holds any entered polymorphic JsonTypeInfo metadata.
        public JsonTypeInfo? PolymorphicJsonTypeInfo;
 
        // Gets the initial JsonTypeInfo metadata used when deserializing the current value.
        public JsonTypeInfo BaseJsonTypeInfo
            => PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntryStarted
                ? PolymorphicJsonTypeInfo!
                : JsonTypeInfo;
 
        // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
        public int PropertyIndex;
 
        // 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.
        public ArgumentState? CtorArgumentState;
 
        // Whether to use custom number handling.
        public JsonNumberHandling? NumberHandling;
 
        // Represents known (non-extension) properties which have value assigned.
        // Each bit corresponds to a property.
        // False means that property is not set (not yet occurred in the payload).
        // Length of the BitArray is equal to number of non-extension properties.
        // Every JsonPropertyInfo has PropertyIndex property which maps to an index in this BitArray.
        public BitArray? AssignedProperties;
 
        // Tracks state related to property population.
        public bool HasParentObject;
        public bool IsPopulating;
 
        public void EndConstructorParameter()
        {
            CtorArgumentState!.JsonParameterInfo = null;
            JsonPropertyName = null;
            PropertyState = StackFramePropertyState.None;
        }
 
        public void EndProperty()
        {
            JsonPropertyInfo = null!;
            JsonPropertyName = null;
            JsonPropertyNameAsString = null;
            PropertyState = StackFramePropertyState.None;
 
            // No need to clear these since they are overwritten each time:
            //  NumberHandling
            //  UseExtensionProperty
        }
 
        public void EndElement()
        {
            JsonPropertyNameAsString = null;
            PropertyState = StackFramePropertyState.None;
        }
 
        /// <summary>
        /// Is the current object a Dictionary.
        /// </summary>
        public bool IsProcessingDictionary()
        {
            return JsonTypeInfo.Kind is JsonTypeInfoKind.Dictionary;
        }
 
        /// <summary>
        /// Is the current object an Enumerable.
        /// </summary>
        public bool IsProcessingEnumerable()
        {
            return JsonTypeInfo.Kind is JsonTypeInfoKind.Enumerable;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void MarkPropertyAsRead(JsonPropertyInfo propertyInfo)
        {
            if (AssignedProperties is { })
            {
                if (!propertyInfo.Options.AllowDuplicateProperties)
                {
                    if (AssignedProperties[propertyInfo.PropertyIndex])
                    {
                        ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(propertyInfo);
                    }
                }
 
                AssignedProperties[propertyInfo.PropertyIndex] = true;
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void InitializePropertiesValidationState(JsonTypeInfo typeInfo)
        {
            Debug.Assert(AssignedProperties is null);
 
            if (typeInfo.ShouldTrackRequiredProperties || typeInfo.Options.AllowDuplicateProperties is false)
            {
                // This may be slightly larger than required (e.g. if there's an extension property)
                AssignedProperties = new BitArray(typeInfo.Properties.Count);
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void ValidateAllRequiredPropertiesAreRead(JsonTypeInfo typeInfo)
        {
            if (typeInfo.ShouldTrackRequiredProperties)
            {
                Debug.Assert(AssignedProperties is not null);
                Debug.Assert(typeInfo.OptionalPropertiesMask is not null);
 
                // All properties must be either assigned or optional
                BitArray assignedOrNotRequiredPropertiesSet = AssignedProperties.Or(typeInfo.OptionalPropertiesMask);
 
                if (!assignedOrNotRequiredPropertiesSet.HasAllSet())
                {
                    ThrowHelper.ThrowJsonException_JsonRequiredPropertyMissing(typeInfo, assignedOrNotRequiredPropertiesSet);
                }
            }
 
            AssignedProperties = null;
        }
 
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string DebuggerDisplay => $"ConverterStrategy.{JsonTypeInfo?.Converter.ConverterStrategy}, {JsonTypeInfo?.Type.Name}";
    }
}