|
// 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.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Pipelines;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text.Json.Reflection;
using System.Text.Json.Serialization.Converters;
using System.Threading;
using System.Threading.Tasks;
namespace System.Text.Json.Serialization.Metadata
{
/// <summary>
/// Provides JSON serialization-related metadata about a type.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public abstract partial class JsonTypeInfo
{
internal const string MetadataFactoryRequiresUnreferencedCode = "JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.";
internal const string JsonObjectTypeName = "System.Text.Json.Nodes.JsonObject";
internal delegate T ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>(TArg0? arg0, TArg1? arg1, TArg2? arg2, TArg3? arg3);
/// <summary>
/// Indices of required properties.
/// </summary>
internal int NumberOfRequiredProperties { get; private set; }
private Action<object>? _onSerializing;
private Action<object>? _onSerialized;
private Action<object>? _onDeserializing;
private Action<object>? _onDeserialized;
internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
{
Type = type;
Options = options;
Converter = converter;
Kind = GetTypeInfoKind(type, converter);
PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo();
ElementType = converter.ElementType;
KeyType = converter.KeyType;
}
/// <summary>
/// Gets the element type corresponding to an enumerable, dictionary or optional type.
/// </summary>
/// <remarks>
/// Returns the element type for enumerable types, the value type for dictionary types,
/// and the underlying type for <see cref="Nullable{T}"/> or F# optional types.
///
/// Returns <see langword="null"/> for all other types or types using custom converters.
/// </remarks>
public Type? ElementType { get; }
/// <summary>
/// Gets the key type corresponding to a dictionary type.
/// </summary>
/// <remarks>
/// Returns the key type for dictionary types.
///
/// Returns <see langword="null"/> for all other types or types using custom converters.
/// </remarks>
public Type? KeyType { get; }
/// <summary>
/// Gets or sets a parameterless factory to be used on deserialization.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// A parameterless factory is not supported for the current metadata <see cref="Kind"/>.
/// </exception>
/// <remarks>
/// If set to <see langword="null" />, any attempt to deserialize instances of the given type will result in an exception.
///
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// types with a single default constructor or default constructors annotated with <see cref="JsonConstructorAttribute"/>
/// will be mapped to this delegate.
/// </remarks>
public Func<object>? CreateObject
{
get => _createObject;
set
{
SetCreateObject(value);
}
}
private protected abstract void SetCreateObject(Delegate? createObject);
private protected Func<object>? _createObject;
internal Func<object>? CreateObjectForExtensionDataProperty { get; set; }
/// <summary>
/// Gets or sets a callback to be invoked before serialization occurs.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Serialization callbacks are only supported for <see cref="JsonTypeInfoKind.Object"/> metadata.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="IJsonOnSerializing"/> implementation on the type.
/// </remarks>
public Action<object>? OnSerializing
{
get => _onSerializing;
set
{
VerifyMutable();
if (Kind is not (JsonTypeInfoKind.Object or JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary))
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
_onSerializing = value;
}
}
/// <summary>
/// Gets or sets a callback to be invoked after serialization occurs.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Serialization callbacks are only supported for <see cref="JsonTypeInfoKind.Object"/> metadata.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="IJsonOnSerialized"/> implementation on the type.
/// </remarks>
public Action<object>? OnSerialized
{
get => _onSerialized;
set
{
VerifyMutable();
if (Kind is not (JsonTypeInfoKind.Object or JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary))
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
_onSerialized = value;
}
}
/// <summary>
/// Gets or sets a callback to be invoked before deserialization occurs.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Serialization callbacks are only supported for <see cref="JsonTypeInfoKind.Object"/> metadata.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="IJsonOnDeserializing"/> implementation on the type.
/// </remarks>
public Action<object>? OnDeserializing
{
get => _onDeserializing;
set
{
VerifyMutable();
if (Kind is not (JsonTypeInfoKind.Object or JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary))
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
if (Converter.IsConvertibleCollection)
{
// The values for convertible collections aren't available at the start of deserialization.
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOnDeserializingCallbacksNotSupported(Type);
}
_onDeserializing = value;
}
}
/// <summary>
/// Gets or sets a callback to be invoked after deserialization occurs.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Serialization callbacks are only supported for <see cref="JsonTypeInfoKind.Object"/> metadata.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="IJsonOnDeserialized"/> implementation on the type.
/// </remarks>
public Action<object>? OnDeserialized
{
get => _onDeserialized;
set
{
VerifyMutable();
if (Kind is not (JsonTypeInfoKind.Object or JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary))
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
_onDeserialized = value;
}
}
/// <summary>
/// Gets the list of <see cref="JsonPropertyInfo"/> metadata corresponding to the current type.
/// </summary>
/// <remarks>
/// Property is only applicable to metadata of kind <see cref="JsonTypeInfoKind.Object"/>.
/// For other kinds an empty, read-only list will be returned.
///
/// The order of <see cref="JsonPropertyInfo"/> entries in the list determines the serialization order,
/// unless either of the entries specifies a non-zero <see cref="JsonPropertyInfo.Order"/> value,
/// in which case the properties will be stable sorted by <see cref="JsonPropertyInfo.Order"/>.
///
/// It is required that added <see cref="JsonPropertyInfo"/> entries are unique up to <see cref="JsonPropertyInfo.Name"/>,
/// however this will only be validated on serialization, once the metadata instance gets locked for further modification.
/// </remarks>
public IList<JsonPropertyInfo> Properties => PropertyList;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal JsonPropertyInfoList PropertyList
{
get
{
return _properties ?? CreatePropertyList();
JsonPropertyInfoList CreatePropertyList()
{
var list = new JsonPropertyInfoList(this);
if (_sourceGenDelayedPropertyInitializer is { } propInit)
{
// .NET 6 source gen backward compatibility -- ensure that the
// property initializer delegate is invoked lazily.
JsonMetadataServices.PopulateProperties(this, list, propInit);
}
JsonPropertyInfoList? result = Interlocked.CompareExchange(ref _properties, list, null);
_sourceGenDelayedPropertyInitializer = null;
return result ?? list;
}
}
}
/// <summary>
/// Stores the .NET 6-style property initialization delegate for delayed evaluation.
/// </summary>
internal Func<JsonSerializerContext, JsonPropertyInfo[]>? SourceGenDelayedPropertyInitializer
{
get => _sourceGenDelayedPropertyInitializer;
set
{
Debug.Assert(!IsReadOnly);
Debug.Assert(_properties is null, "must not be set if a property list has been initialized.");
_sourceGenDelayedPropertyInitializer = value;
}
}
private Func<JsonSerializerContext, JsonPropertyInfo[]>? _sourceGenDelayedPropertyInitializer;
private JsonPropertyInfoList? _properties;
/// <summary>
/// Gets or sets a configuration object specifying polymorphism metadata.
/// </summary>
/// <exception cref="ArgumentException">
/// <paramref name="value" /> has been associated with a different <see cref="JsonTypeInfo"/> instance.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Polymorphic serialization is not supported for the current metadata <see cref="Kind"/>.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the configuration of this setting will be mapped from any <see cref="JsonDerivedTypeAttribute"/> or <see cref="JsonPolymorphicAttribute"/> annotations.
/// </remarks>
public JsonPolymorphismOptions? PolymorphismOptions
{
get => _polymorphismOptions;
set
{
VerifyMutable();
if (value != null)
{
if (Kind == JsonTypeInfoKind.None)
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
if (value.DeclaringTypeInfo != null && value.DeclaringTypeInfo != this)
{
ThrowHelper.ThrowArgumentException_JsonPolymorphismOptionsAssociatedWithDifferentJsonTypeInfo(nameof(value));
}
value.DeclaringTypeInfo = this;
}
_polymorphismOptions = value;
}
}
/// <summary>
/// Specifies whether the current instance has been locked for modification.
/// </summary>
/// <remarks>
/// A <see cref="JsonTypeInfo"/> instance can be locked either if
/// it has been passed to one of the <see cref="JsonSerializer"/> methods,
/// has been associated with a <see cref="JsonSerializerContext"/> instance,
/// or a user explicitly called the <see cref="MakeReadOnly"/> method on the instance.
/// </remarks>
public bool IsReadOnly { get; private set; }
/// <summary>
/// Locks the current instance for further modification.
/// </summary>
/// <remarks>This method is idempotent.</remarks>
public void MakeReadOnly() => IsReadOnly = true;
private protected JsonPolymorphismOptions? _polymorphismOptions;
internal object? CreateObjectWithArgs { get; set; }
// Add method delegate for non-generic Stack and Queue; and types that derive from them.
internal object? AddMethodDelegate { get; set; }
internal JsonPropertyInfo? ExtensionDataProperty { get; private set; }
internal PolymorphicTypeResolver? PolymorphicTypeResolver { get; private set; }
// Indicates that SerializeHandler is populated.
internal bool HasSerializeHandler { get; private protected set; }
// Indicates that SerializeHandler is populated and is compatible with the associated contract metadata.
internal bool CanUseSerializeHandler { get; private set; }
// Configure would normally have thrown why initializing properties for source gen but type had SerializeHandler
// so it is allowed to be used for fast-path serialization but it will throw if used for metadata-based serialization
internal bool PropertyMetadataSerializationNotSupported { get; set; }
internal bool IsNullable => Converter.NullableElementConverter is not null;
internal bool CanBeNull => PropertyInfoForTypeInfo.PropertyTypeCanBeNull;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ValidateCanBeUsedForPropertyMetadataSerialization()
{
if (PropertyMetadataSerializationNotSupported)
{
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type);
}
}
/// <summary>
/// Return the JsonTypeInfo for the element type, or null if the type is not an enumerable or dictionary.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal JsonTypeInfo? ElementTypeInfo
{
get
{
Debug.Assert(IsConfigured);
Debug.Assert(_elementTypeInfo is null or { IsConfigurationStarted: true });
// Even though this instance has already been configured,
// it is possible for contending threads to call the property
// while the wider JsonTypeInfo graph is still being configured.
// Call EnsureConfigured() to force synchronization if necessary.
JsonTypeInfo? elementTypeInfo = _elementTypeInfo;
elementTypeInfo?.EnsureConfigured();
return elementTypeInfo;
}
set
{
Debug.Assert(!IsReadOnly);
Debug.Assert(value is null || value.Type == ElementType);
_elementTypeInfo = value;
}
}
/// <summary>
/// Return the JsonTypeInfo for the key type, or null if the type is not a dictionary.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal JsonTypeInfo? KeyTypeInfo
{
get
{
Debug.Assert(IsConfigured);
Debug.Assert(_keyTypeInfo is null or { IsConfigurationStarted: true });
// Even though this instance has already been configured,
// it is possible for contending threads to call the property
// while the wider JsonTypeInfo graph is still being configured.
// Call EnsureConfigured() to force synchronization if necessary.
JsonTypeInfo? keyTypeInfo = _keyTypeInfo;
keyTypeInfo?.EnsureConfigured();
return keyTypeInfo;
}
set
{
Debug.Assert(!IsReadOnly);
Debug.Assert(value is null || value.Type == KeyType);
_keyTypeInfo = value;
}
}
private JsonTypeInfo? _elementTypeInfo;
private JsonTypeInfo? _keyTypeInfo;
/// <summary>
/// Gets the <see cref="JsonSerializerOptions"/> value associated with the current <see cref="JsonTypeInfo" /> instance.
/// </summary>
public JsonSerializerOptions Options { get; }
/// <summary>
/// Gets the <see cref="Type"/> for which the JSON serialization contract is being defined.
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the <see cref="JsonConverter"/> associated with the current type.
/// </summary>
/// <remarks>
/// The <see cref="JsonConverter"/> associated with the type determines the value of <see cref="Kind"/>,
/// and by extension the types of metadata that are configurable in the current JSON contract.
/// As such, the value of the converter cannot be changed once a <see cref="JsonTypeInfo"/> instance has been created.
/// </remarks>
public JsonConverter Converter { get; }
/// <summary>
/// Determines the kind of contract metadata that the current instance is specifying.
/// </summary>
/// <remarks>
/// The value of <see cref="Kind"/> determines what aspects of the JSON contract are configurable.
/// For example, it is only possible to configure the <see cref="Properties"/> list for metadata
/// of kind <see cref="JsonTypeInfoKind.Object"/>.
///
/// The value of <see cref="Kind"/> is determined exclusively by the <see cref="JsonConverter"/>
/// resolved for the current type, and cannot be changed once resolution has happened.
/// User-defined custom converters (specified either via <see cref="JsonConverterAttribute"/> or <see cref="JsonSerializerOptions.Converters"/>)
/// are metadata-agnostic and thus always resolve to <see cref="JsonTypeInfoKind.None"/>.
/// </remarks>
public JsonTypeInfoKind Kind { get; }
/// <summary>
/// Dummy <see cref="JsonPropertyInfo"/> instance corresponding to the declaring type of this <see cref="JsonTypeInfo"/>.
/// </summary>
/// <remarks>
/// Used as convenience in cases where we want to serialize property-like values that do not define property metadata, such as:
/// 1. a collection element type,
/// 2. a dictionary key or value type or,
/// 3. the property metadata for the root-level value.
/// For example, for a property returning <see cref="List{T}"/> where T is a string,
/// a JsonTypeInfo will be created with .Type=typeof(string) and .PropertyInfoForTypeInfo=JsonPropertyInfo{string}.
/// </remarks>
internal JsonPropertyInfo PropertyInfoForTypeInfo { get; }
private protected abstract JsonPropertyInfo CreatePropertyInfoForTypeInfo();
/// <summary>
/// Gets or sets the type-level <see cref="JsonSerializerOptions.NumberHandling"/> override.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Specified an invalid <see cref="JsonNumberHandling"/> value.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="JsonNumberHandlingAttribute"/> annotations.
/// </remarks>
public JsonNumberHandling? NumberHandling
{
get => _numberHandling;
set
{
VerifyMutable();
if (value is not null && !JsonSerializer.IsValidNumberHandlingValue(value.Value))
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_numberHandling = value;
}
}
internal JsonNumberHandling EffectiveNumberHandling => _numberHandling ?? Options.NumberHandling;
private JsonNumberHandling? _numberHandling;
/// <summary>
/// Gets or sets the type-level <see cref="JsonUnmappedMemberHandling"/> override.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Unmapped member handling only supported for <see cref="JsonTypeInfoKind.Object"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Specified an invalid <see cref="JsonUnmappedMemberHandling"/> value.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from any <see cref="JsonUnmappedMemberHandlingAttribute"/> annotations.
/// </remarks>
public JsonUnmappedMemberHandling? UnmappedMemberHandling
{
get => _unmappedMemberHandling;
set
{
VerifyMutable();
if (Kind != JsonTypeInfoKind.Object)
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
if (value is not null && !JsonSerializer.IsValidUnmappedMemberHandlingValue(value.Value))
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_unmappedMemberHandling = value;
}
}
private JsonUnmappedMemberHandling? _unmappedMemberHandling;
internal JsonUnmappedMemberHandling EffectiveUnmappedMemberHandling { get; private set; }
private JsonObjectCreationHandling? _preferredPropertyObjectCreationHandling;
/// <summary>
/// Gets or sets the preferred <see cref="JsonObjectCreationHandling"/> value for properties contained in the type.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
///
/// -or-
///
/// Unmapped member handling only supported for <see cref="JsonTypeInfoKind.Object"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Specified an invalid <see cref="JsonObjectCreationHandling"/> value.
/// </exception>
/// <remarks>
/// For contracts originating from <see cref="DefaultJsonTypeInfoResolver"/> or <see cref="JsonSerializerContext"/>,
/// the value of this callback will be mapped from <see cref="JsonObjectCreationHandlingAttribute"/> annotations on types.
/// </remarks>
public JsonObjectCreationHandling? PreferredPropertyObjectCreationHandling
{
get => _preferredPropertyObjectCreationHandling;
set
{
VerifyMutable();
if (Kind != JsonTypeInfoKind.Object)
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
}
if (value is not null && !JsonSerializer.IsValidCreationHandlingValue(value.Value))
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_preferredPropertyObjectCreationHandling = value;
}
}
/// <summary>
/// Gets or sets the <see cref="IJsonTypeInfoResolver"/> from which this metadata instance originated.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonTypeInfo"/> instance has been locked for further modification.
/// </exception>
/// <remarks>
/// Metadata used to determine the <see cref="JsonSerializerContext.GeneratedSerializerOptions"/>
/// configuration for the current metadata instance.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public IJsonTypeInfoResolver? OriginatingResolver
{
get => _originatingResolver;
set
{
VerifyMutable();
if (value is JsonSerializerContext)
{
// The source generator uses this property setter to brand the metadata instance as user-unmodified.
// Even though users could call the same property setter to unset this flag, this is generally speaking fine.
// This flag is only used to determine fast-path invalidation, worst case scenario this would lead to a false negative.
IsCustomized = false;
}
_originatingResolver = value;
}
}
private IJsonTypeInfoResolver? _originatingResolver;
/// <summary>
/// Gets or sets an attribute provider corresponding to the deserialization constructor.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="JsonPropertyInfo"/> instance has been locked for further modification.
/// </exception>
/// <remarks>
/// When resolving metadata via the built-in resolvers this will be populated with
/// the underlying <see cref="ConstructorInfo" /> of the serialized property or field.
/// </remarks>
public ICustomAttributeProvider? ConstructorAttributeProvider
{
get
{
Func<ICustomAttributeProvider>? ctorAttrProviderFactory = Volatile.Read(ref ConstructorAttributeProviderFactory);
ICustomAttributeProvider? ctorAttrProvider = _constructorAttributeProvider;
if (ctorAttrProvider is null && ctorAttrProviderFactory is not null)
{
_constructorAttributeProvider = ctorAttrProvider = ctorAttrProviderFactory();
Volatile.Write(ref ConstructorAttributeProviderFactory, null);
}
return ctorAttrProvider;
}
internal set
{
Debug.Assert(!IsReadOnly);
_constructorAttributeProvider = value;
Volatile.Write(ref ConstructorAttributeProviderFactory, null);
}
}
// Metadata emanating from the source generator use delayed attribute provider initialization
// ensuring that reflection metadata resolution remains pay-for-play and is trimmable.
internal Func<ICustomAttributeProvider>? ConstructorAttributeProviderFactory;
private ICustomAttributeProvider? _constructorAttributeProvider;
internal void VerifyMutable()
{
if (IsReadOnly)
{
ThrowHelper.ThrowInvalidOperationException_TypeInfoImmutable();
}
IsCustomized = true;
}
/// <summary>
/// Indicates that the current JsonTypeInfo might contain user modifications.
/// Defaults to true, and is only unset by the built-in contract resolvers.
/// </summary>
internal bool IsCustomized { get; set; } = true;
internal bool IsConfigured => _configurationState == ConfigurationState.Configured;
internal bool IsConfigurationStarted => _configurationState is not ConfigurationState.NotConfigured;
private volatile ConfigurationState _configurationState;
private enum ConfigurationState : byte
{
NotConfigured = 0,
Configuring = 1,
Configured = 2
};
private ExceptionDispatchInfo? _cachedConfigureError;
internal void EnsureConfigured()
{
if (!IsConfigured)
ConfigureSynchronized();
void ConfigureSynchronized()
{
Options.MakeReadOnly();
MakeReadOnly();
_cachedConfigureError?.Throw();
lock (Options.CacheContext)
{
if (_configurationState != ConfigurationState.NotConfigured)
{
// The value of _configurationState is either
// 'Configuring': recursive instance configured by this thread or
// 'Configured' : instance already configured by another thread.
// We can safely yield the configuration operation in both cases.
return;
}
_cachedConfigureError?.Throw();
try
{
_configurationState = ConfigurationState.Configuring;
Configure();
_configurationState = ConfigurationState.Configured;
}
catch (Exception e)
{
_cachedConfigureError = ExceptionDispatchInfo.Capture(e);
_configurationState = ConfigurationState.NotConfigured;
throw;
}
}
}
}
private void Configure()
{
Debug.Assert(Monitor.IsEntered(Options.CacheContext), "Configure called directly, use EnsureConfigured which synchronizes access to this method");
Debug.Assert(Options.IsReadOnly);
Debug.Assert(IsReadOnly);
PropertyInfoForTypeInfo.Configure();
if (PolymorphismOptions != null)
{
// This needs to be done before ConfigureProperties() is called
// JsonPropertyInfo.Configure() must have this value available in order to detect Polymoprhic + cyclic class case
PolymorphicTypeResolver = new PolymorphicTypeResolver(Options, PolymorphismOptions, Type, Converter.CanHaveMetadata);
}
if (Kind == JsonTypeInfoKind.Object)
{
ConfigureProperties();
if (DetermineUsesParameterizedConstructor())
{
ConfigureConstructorParameters();
}
}
if (ElementType != null)
{
_elementTypeInfo ??= Options.GetTypeInfoInternal(ElementType);
_elementTypeInfo.EnsureConfigured();
}
if (KeyType != null)
{
_keyTypeInfo ??= Options.GetTypeInfoInternal(KeyType);
_keyTypeInfo.EnsureConfigured();
}
DetermineIsCompatibleWithCurrentOptions();
CanUseSerializeHandler = HasSerializeHandler && IsCompatibleWithCurrentOptions;
}
/// <summary>
/// Gets any ancestor polymorphic types that declare
/// a type discriminator for the current type. Consulted
/// when serializing polymorphic values as objects.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal JsonTypeInfo? AncestorPolymorphicType
{
get
{
Debug.Assert(IsConfigured);
Debug.Assert(Type != typeof(object));
if (!_isAncestorPolymorphicTypeResolved)
{
_ancestorPolymorhicType = PolymorphicTypeResolver.FindNearestPolymorphicBaseType(this);
_isAncestorPolymorphicTypeResolved = true;
}
return _ancestorPolymorhicType;
}
}
private JsonTypeInfo? _ancestorPolymorhicType;
private volatile bool _isAncestorPolymorphicTypeResolved;
/// <summary>
/// Determines if the transitive closure of all JsonTypeInfo metadata referenced
/// by the current type (property types, key types, element types, ...) are
/// compatible with the settings as specified in JsonSerializerOptions.
/// </summary>
private void DetermineIsCompatibleWithCurrentOptions()
{
// Defines a recursive algorithm validating that the `IsCurrentNodeCompatible`
// predicate is valid for every node in the type graph. This method only checks
// the immediate children, with recursion being driven by the Configure() method.
// Therefore, this method must be called _after_ the child nodes have been configured.
Debug.Assert(IsReadOnly);
Debug.Assert(!IsConfigured);
if (!IsCurrentNodeCompatible())
{
IsCompatibleWithCurrentOptions = false;
return;
}
if (_properties != null)
{
foreach (JsonPropertyInfo property in _properties)
{
Debug.Assert(property.IsConfigured);
if (!property.IsPropertyTypeInfoConfigured)
{
// Either an ignored property or property is part of a cycle.
// In both cases we can ignore these instances.
continue;
}
if (!property.JsonTypeInfo.IsCompatibleWithCurrentOptions)
{
IsCompatibleWithCurrentOptions = false;
return;
}
}
}
if (_elementTypeInfo?.IsCompatibleWithCurrentOptions == false ||
_keyTypeInfo?.IsCompatibleWithCurrentOptions == false)
{
IsCompatibleWithCurrentOptions = false;
return;
}
Debug.Assert(IsCompatibleWithCurrentOptions);
// Defines the core predicate that must be checked for every node in the type graph.
bool IsCurrentNodeCompatible()
{
if (IsCustomized)
{
// Return false if we have detected contract customization by the user.
return false;
}
if (Options.CanUseFastPathSerializationLogic)
{
// Simple case/backward compatibility: options uses a combination of compatible built-in converters.
return true;
}
return OriginatingResolver.IsCompatibleWithOptions(Options);
}
}
/// <summary>
/// Holds the result of the above algorithm -- NB must default to true
/// to establish a base case for recursive types and any JsonIgnored property types.
/// </summary>
private bool IsCompatibleWithCurrentOptions { get; set; } = true;
/// <summary>
/// Determine if the current configuration is compatible with using a parameterized constructor.
/// </summary>
internal bool DetermineUsesParameterizedConstructor()
=> Converter.ConstructorIsParameterized && CreateObject is null;
/// <summary>
/// Creates a blank <see cref="JsonTypeInfo{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type for which contract metadata is specified.</typeparam>
/// <param name="options">The <see cref="JsonSerializerOptions"/> instance the metadata is associated with.</param>
/// <returns>A blank <see cref="JsonTypeInfo{T}"/> instance.</returns>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is null.</exception>
/// <remarks>
/// The returned <see cref="JsonTypeInfo{T}"/> will be blank, with the exception of the
/// <see cref="Converter"/> property which will be resolved either from
/// <see cref="JsonSerializerOptions.Converters"/> or the built-in converters for the type.
/// Any converters specified via <see cref="JsonConverterAttribute"/> on the type declaration
/// will not be resolved by this method.
///
/// What converter does get resolved influences the value of <see cref="Kind"/>,
/// which constrains the type of metadata that can be modified in the <see cref="JsonTypeInfo"/> instance.
/// </remarks>
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public static JsonTypeInfo<T> CreateJsonTypeInfo<T>(JsonSerializerOptions options)
{
if (options == null)
{
ThrowHelper.ThrowArgumentNullException(nameof(options));
}
JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(typeof(T), options, resolveJsonConverterAttribute: false);
return new JsonTypeInfo<T>(converter, options);
}
/// <summary>
/// Creates a blank <see cref="JsonTypeInfo"/> instance.
/// </summary>
/// <param name="type">The type for which contract metadata is specified.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> instance the metadata is associated with.</param>
/// <returns>A blank <see cref="JsonTypeInfo"/> instance.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="options"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="type"/> cannot be used for serialization.</exception>
/// <remarks>
/// The returned <see cref="JsonTypeInfo"/> will be blank, with the exception of the
/// <see cref="Converter"/> property which will be resolved either from
/// <see cref="JsonSerializerOptions.Converters"/> or the built-in converters for the type.
/// Any converters specified via <see cref="JsonConverterAttribute"/> on the type declaration
/// will not be resolved by this method.
///
/// What converter does get resolved influences the value of <see cref="Kind"/>,
/// which constrains the type of metadata that can be modified in the <see cref="JsonTypeInfo"/> instance.
/// </remarks>
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options)
{
if (type == null)
{
ThrowHelper.ThrowArgumentNullException(nameof(type));
}
if (options == null)
{
ThrowHelper.ThrowArgumentNullException(nameof(options));
}
if (IsInvalidForSerialization(type))
{
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
}
JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(type, options, resolveJsonConverterAttribute: false);
return CreateJsonTypeInfo(type, converter, options);
}
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
internal static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo;
if (converter.Type == type)
{
// For performance, avoid doing a reflection-based instantiation
// if the converter type matches that of the declared type.
jsonTypeInfo = converter.CreateJsonTypeInfo(options);
}
else
{
Type jsonTypeInfoType = typeof(JsonTypeInfo<>).MakeGenericType(type);
jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
parameterTypes: [typeof(JsonConverter), typeof(JsonSerializerOptions)],
parameters: new object[] { converter, options })!;
}
Debug.Assert(jsonTypeInfo.Type == type);
return jsonTypeInfo;
}
/// <summary>
/// Creates a blank <see cref="JsonPropertyInfo"/> instance for the current <see cref="JsonTypeInfo"/>.
/// </summary>
/// <param name="propertyType">The declared type for the property.</param>
/// <param name="name">The property name used in JSON serialization and deserialization.</param>
/// <returns>A blank <see cref="JsonPropertyInfo"/> instance.</returns>
/// <exception cref="ArgumentNullException"><paramref name="propertyType"/> or <paramref name="name"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="propertyType"/> cannot be used for serialization.</exception>
/// <exception cref="InvalidOperationException">The <see cref="JsonTypeInfo"/> instance has been locked for further modification.</exception>
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
{
if (propertyType == null)
{
ThrowHelper.ThrowArgumentNullException(nameof(propertyType));
}
if (name == null)
{
ThrowHelper.ThrowArgumentNullException(nameof(name));
}
if (IsInvalidForSerialization(propertyType))
{
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(propertyType), propertyType, Type, name);
}
VerifyMutable();
JsonPropertyInfo propertyInfo = CreatePropertyUsingReflection(propertyType, declaringType: null);
propertyInfo.Name = name;
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;
// Untyped, root-level serialization methods
internal abstract void SerializeAsObject(Utf8JsonWriter writer, object? rootValue);
internal abstract Task SerializeAsObjectAsync(PipeWriter pipeWriter, object? rootValue, int flushThreshold, CancellationToken cancellationToken);
internal abstract Task SerializeAsObjectAsync(Stream utf8Json, object? rootValue, CancellationToken cancellationToken);
internal abstract Task SerializeAsObjectAsync(PipeWriter utf8Json, object? rootValue, CancellationToken cancellationToken);
internal abstract void SerializeAsObject(Stream utf8Json, object? rootValue);
// Untyped, root-level deserialization methods
internal abstract object? DeserializeAsObject(ref Utf8JsonReader reader, ref ReadStack state);
internal abstract ValueTask<object?> DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken);
internal abstract object? DeserializeAsObject(Stream utf8Json);
internal ref struct PropertyHierarchyResolutionState(JsonSerializerOptions options)
{
public Dictionary<string, (JsonPropertyInfo, int index)> AddedProperties = new(options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
public Dictionary<string, JsonPropertyInfo>? IgnoredProperties;
public bool IsPropertyOrderSpecified;
}
private protected readonly struct ParameterLookupKey(Type type, string name) : IEquatable<ParameterLookupKey>
{
public Type Type { get; } = type;
public string Name { get; } = name;
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Name);
public bool Equals(ParameterLookupKey other) => Type == other.Type && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
public override bool Equals([NotNullWhen(true)] object? obj) => obj is ParameterLookupKey key && Equals(key);
}
internal void ConfigureProperties()
{
Debug.Assert(Kind == JsonTypeInfoKind.Object);
Debug.Assert(_propertyCache is null);
Debug.Assert(_propertyIndex is null);
Debug.Assert(ExtensionDataProperty is null);
JsonPropertyInfoList properties = PropertyList;
StringComparer comparer = Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
Dictionary<string, JsonPropertyInfo> propertyIndex = new(properties.Count, comparer);
List<JsonPropertyInfo> propertyCache = new(properties.Count);
int numberOfRequiredProperties = 0;
bool arePropertiesSorted = true;
int previousPropertyOrder = int.MinValue;
foreach (JsonPropertyInfo property in properties)
{
Debug.Assert(property.DeclaringTypeInfo == this);
if (property.IsExtensionData)
{
if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
{
ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
}
if (ExtensionDataProperty != null)
{
ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
}
ExtensionDataProperty = property;
}
else
{
if (property.IsRequired)
{
property.RequiredPropertyIndex = numberOfRequiredProperties++;
}
if (arePropertiesSorted)
{
arePropertiesSorted = previousPropertyOrder <= property.Order;
previousPropertyOrder = property.Order;
}
if (!propertyIndex.TryAdd(property.Name, property))
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, property.Name);
}
propertyCache.Add(property);
}
property.Configure();
}
if (!arePropertiesSorted)
{
// Properties have been configured by the user and require sorting.
properties.SortProperties();
propertyCache.StableSortByKey(static propInfo => propInfo.Order);
}
NumberOfRequiredProperties = numberOfRequiredProperties;
_propertyCache = propertyCache.ToArray();
_propertyIndex = propertyIndex;
// Override global UnmappedMemberHandling configuration
// if type specifies an extension data property.
EffectiveUnmappedMemberHandling = UnmappedMemberHandling ??
(ExtensionDataProperty is null
? Options.UnmappedMemberHandling
: JsonUnmappedMemberHandling.Skip);
}
internal void PopulateParameterInfoValues(JsonParameterInfoValues[] parameterInfoValues)
{
if (parameterInfoValues.Length == 0)
{
return;
}
Dictionary<ParameterLookupKey, JsonParameterInfoValues> parameterIndex = new(parameterInfoValues.Length);
foreach (JsonParameterInfoValues parameterInfoValue in parameterInfoValues)
{
ParameterLookupKey paramKey = new(parameterInfoValue.ParameterType, parameterInfoValue.Name);
parameterIndex.TryAdd(paramKey, parameterInfoValue); // Ignore conflicts since they are reported at serialization time.
}
ParameterCount = parameterInfoValues.Length;
_parameterInfoValuesIndex = parameterIndex;
}
internal void ResolveMatchingParameterInfo(JsonPropertyInfo propertyInfo)
{
Debug.Assert(
CreateObjectWithArgs is null || _parameterInfoValuesIndex is not null,
"Metadata with parameterized constructors must have populated parameter info metadata.");
if (_parameterInfoValuesIndex is not { } index)
{
return;
}
string propertyName = propertyInfo.MemberName ?? propertyInfo.Name;
ParameterLookupKey propKey = new(propertyInfo.PropertyType, propertyName);
if (index.TryGetValue(propKey, out JsonParameterInfoValues? matchingParameterInfoValues))
{
propertyInfo.AddJsonParameterInfo(matchingParameterInfoValues);
}
}
internal void ConfigureConstructorParameters()
{
Debug.Assert(Kind == JsonTypeInfoKind.Object);
Debug.Assert(DetermineUsesParameterizedConstructor());
Debug.Assert(_propertyCache is not null);
Debug.Assert(_parameterCache is null);
List<JsonParameterInfo> parameterCache = new(ParameterCount);
Dictionary<ParameterLookupKey, JsonParameterInfo> parameterIndex = new(ParameterCount);
foreach (JsonPropertyInfo propertyInfo in _propertyCache)
{
JsonParameterInfo? parameterInfo = propertyInfo.AssociatedParameter;
if (parameterInfo is null)
{
continue;
}
string propertyName = propertyInfo.MemberName ?? propertyInfo.Name;
ParameterLookupKey paramKey = new(propertyInfo.PropertyType, propertyName);
if (!parameterIndex.TryAdd(paramKey, parameterInfo))
{
// Multiple object properties cannot bind to the same constructor parameter.
ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters(
Type,
parameterInfo.Name,
propertyInfo.Name,
parameterIndex[paramKey].MatchingProperty.Name);
}
parameterCache.Add(parameterInfo);
}
if (ExtensionDataProperty is { AssociatedParameter: not null })
{
Debug.Assert(ExtensionDataProperty.MemberName != null, "Custom property info cannot be data extension property");
ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(ExtensionDataProperty.MemberName, ExtensionDataProperty);
}
_parameterCache = parameterCache.ToArray();
_parameterInfoValuesIndex = null;
}
internal static void ValidateType(Type type)
{
if (IsInvalidForSerialization(type))
{
ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(type, declaringType: null, memberInfo: null);
}
}
internal static bool IsInvalidForSerialization(Type type)
{
return type == typeof(void) || type.IsPointer || type.IsByRef || IsByRefLike(type) || type.ContainsGenericParameters;
}
internal void PopulatePolymorphismMetadata()
{
Debug.Assert(!IsReadOnly);
JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type);
if (options != null)
{
options.DeclaringTypeInfo = this;
_polymorphismOptions = options;
}
}
internal void MapInterfaceTypesToCallbacks()
{
Debug.Assert(!IsReadOnly);
if (Kind is JsonTypeInfoKind.Object or JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary)
{
if (typeof(IJsonOnSerializing).IsAssignableFrom(Type))
{
OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing();
}
if (typeof(IJsonOnSerialized).IsAssignableFrom(Type))
{
OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized();
}
if (typeof(IJsonOnDeserializing).IsAssignableFrom(Type))
{
OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing();
}
if (typeof(IJsonOnDeserialized).IsAssignableFrom(Type))
{
OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized();
}
}
}
internal void SetCreateObjectIfCompatible(Delegate? createObject)
{
Debug.Assert(!IsReadOnly);
// Guard against the reflection resolver/source generator attempting to pass
// a CreateObject delegate to converters/metadata that do not support it.
if (Converter.SupportsCreateObjectDelegate && !Converter.ConstructorIsParameterized)
{
SetCreateObject(createObject);
}
}
private static bool IsByRefLike(Type type)
{
#if NET
return type.IsByRefLike;
#else
if (!type.IsValueType)
{
return false;
}
object[] attributes = type.GetCustomAttributes(inherit: false);
for (int i = 0; i < attributes.Length; i++)
{
if (attributes[i].GetType().FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute")
{
return true;
}
}
return false;
#endif
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal bool SupportsPolymorphicDeserialization
{
get
{
Debug.Assert(IsConfigurationStarted);
return PolymorphicTypeResolver?.UsesTypeDiscriminators == true;
}
}
internal static bool IsValidExtensionDataProperty(Type propertyType)
{
return typeof(IDictionary<string, object>).IsAssignableFrom(propertyType) ||
typeof(IDictionary<string, JsonElement>).IsAssignableFrom(propertyType) ||
// Avoid a reference to typeof(JsonNode) to support trimming.
(propertyType.FullName == JsonObjectTypeName && ReferenceEquals(propertyType.Assembly, typeof(JsonTypeInfo).Assembly));
}
private static JsonTypeInfoKind GetTypeInfoKind(Type type, JsonConverter converter)
{
if (type == typeof(object) && converter.CanBePolymorphic)
{
// System.Object is polymorphic and will not respect Properties
Debug.Assert(converter is ObjectConverter);
return JsonTypeInfoKind.None;
}
switch (converter.ConverterStrategy)
{
case ConverterStrategy.Value: return JsonTypeInfoKind.None;
case ConverterStrategy.Object: return JsonTypeInfoKind.Object;
case ConverterStrategy.Enumerable: return JsonTypeInfoKind.Enumerable;
case ConverterStrategy.Dictionary: return JsonTypeInfoKind.Dictionary;
case ConverterStrategy.None:
Debug.Assert(converter is JsonConverterFactory);
ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
return default;
default:
Debug.Fail($"Unexpected class type: {converter.ConverterStrategy}");
throw new InvalidOperationException();
}
}
internal sealed class JsonPropertyInfoList : ConfigurationList<JsonPropertyInfo>
{
private readonly JsonTypeInfo _jsonTypeInfo;
public JsonPropertyInfoList(JsonTypeInfo jsonTypeInfo)
{
_jsonTypeInfo = jsonTypeInfo;
}
public override bool IsReadOnly => _jsonTypeInfo._properties == this && _jsonTypeInfo.IsReadOnly || _jsonTypeInfo.Kind != JsonTypeInfoKind.Object;
protected override void OnCollectionModifying()
{
if (_jsonTypeInfo._properties == this)
{
_jsonTypeInfo.VerifyMutable();
}
if (_jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
{
ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(_jsonTypeInfo.Kind);
}
}
protected override void ValidateAddedValue(JsonPropertyInfo item)
{
item.EnsureChildOf(_jsonTypeInfo);
}
public void SortProperties()
{
_list.StableSortByKey(static propInfo => propInfo.Order);
}
/// <summary>
/// Used by the built-in resolvers to add property metadata applying conflict resolution.
/// </summary>
public void AddPropertyWithConflictResolution(JsonPropertyInfo jsonPropertyInfo, ref PropertyHierarchyResolutionState state)
{
Debug.Assert(!_jsonTypeInfo.IsConfigured);
Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName can be null in custom JsonPropertyInfo instances and should never be passed in this method");
// Algorithm should be kept in sync with the Roslyn equivalent in JsonSourceGenerator.Parser.cs
string memberName = jsonPropertyInfo.MemberName;
if (state.AddedProperties.TryAdd(jsonPropertyInfo.Name, (jsonPropertyInfo, Count)))
{
Add(jsonPropertyInfo);
state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
else
{
// The JsonPropertyNameAttribute or naming policy resulted in a collision.
(JsonPropertyInfo other, int index) = state.AddedProperties[jsonPropertyInfo.Name];
if (other.IsIgnored)
{
// Overwrite previously cached property since it has [JsonIgnore].
state.AddedProperties[jsonPropertyInfo.Name] = (jsonPropertyInfo, index);
this[index] = jsonPropertyInfo;
state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
else
{
bool ignoreCurrentProperty =
// Does the current property have `JsonIgnoreAttribute`?
jsonPropertyInfo.IsIgnored ||
// Is the current property hidden by the previously cached property
// (with `new` keyword, or by overriding)?
jsonPropertyInfo.IsOverriddenOrShadowedBy(other) ||
// Was a property with the same CLR name ignored? That property hid the current property,
// thus, if it was ignored, the current property should be ignored too.
(state.IgnoredProperties?.TryGetValue(memberName, out JsonPropertyInfo? ignored) == true && jsonPropertyInfo.IsOverriddenOrShadowedBy(ignored));
if (!ignoreCurrentProperty)
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(_jsonTypeInfo.Type, jsonPropertyInfo.Name);
}
}
}
if (jsonPropertyInfo.IsIgnored)
{
(state.IgnoredProperties ??= new())[memberName] = jsonPropertyInfo;
}
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"Type = {Type.Name}, Kind = {Kind}";
}
}
|