File: System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.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.CodeAnalysis;
using System.Threading;
 
namespace System.Text.Json.Serialization.Metadata
{
    /// <summary>
    /// Defines the default, reflection-based JSON contract resolver used by System.Text.Json.
    /// </summary>
    /// <remarks>
    /// The contract resolver used by <see cref="JsonSerializerOptions.Default"/>.
    /// </remarks>
    public partial class DefaultJsonTypeInfoResolver : IJsonTypeInfoResolver, IBuiltInJsonTypeInfoResolver
    {
        private bool _mutable;
 
        /// <summary>
        /// Creates a mutable <see cref="DefaultJsonTypeInfoResolver"/> instance.
        /// </summary>
        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
        public DefaultJsonTypeInfoResolver() : this(mutable: true)
        {
        }
 
        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
        private DefaultJsonTypeInfoResolver(bool mutable)
        {
            _mutable = mutable;
        }
 
        /// <summary>
        /// Resolves a JSON contract for a given <paramref name="type"/> and <paramref name="options"/> configuration.
        /// </summary>
        /// <param name="type">The type for which to resolve a JSON contract.</param>
        /// <param name="options">A <see cref="JsonSerializerOptions"/> instance used to determine contract configuration.</param>
        /// <returns>A <see cref="JsonTypeInfo"/> defining a reflection-derived JSON contract for <paramref name="type"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
        /// <remarks>
        /// The base implementation of this method will produce a reflection-derived contract
        /// and apply any callbacks from the <see cref="Modifiers"/> list.
        /// </remarks>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The ctor is marked RequiresUnreferencedCode.")]
        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "The ctor is marked RequiresDynamicCode.")]
        public virtual JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
        {
            if (type == null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(type));
            }
 
            if (options == null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(options));
            }
 
            _mutable = false;
 
            JsonTypeInfo.ValidateType(type);
            JsonTypeInfo typeInfo = CreateJsonTypeInfo(type, options);
            typeInfo.OriginatingResolver = this;
 
            // We've finished configuring the metadata, brand the instance as user-unmodified.
            // This should be the last update operation in the resolver to avoid resetting the flag.
            typeInfo.IsCustomized = false;
 
            if (_modifiers != null)
            {
                foreach (Action<JsonTypeInfo> modifier in _modifiers)
                {
                    modifier(typeInfo);
                }
            }
 
            return typeInfo;
        }
 
        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
        private static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options)
        {
            JsonConverter converter = GetConverterForType(type, options);
            return CreateTypeInfoCore(type, converter, options);
        }
 
        /// <summary>
        /// Gets a list of user-defined callbacks that can be used to modify the initial contract.
        /// </summary>
        /// <remarks>
        /// The modifier list will be rendered immutable after the first <see cref="GetTypeInfo(Type, JsonSerializerOptions)"/> invocation.
        ///
        /// Modifier callbacks are called consecutively in the order in which they are specified in the list.
        /// </remarks>
        public IList<Action<JsonTypeInfo>> Modifiers => _modifiers ??= new ModifierCollection(this);
        private ModifierCollection? _modifiers;
 
        private sealed class ModifierCollection : ConfigurationList<Action<JsonTypeInfo>>
        {
            private readonly DefaultJsonTypeInfoResolver _resolver;
 
            public ModifierCollection(DefaultJsonTypeInfoResolver resolver)
            {
                _resolver = resolver;
            }
 
            public override bool IsReadOnly => !_resolver._mutable;
            protected override void OnCollectionModifying()
            {
                if (!_resolver._mutable)
                {
                    ThrowHelper.ThrowInvalidOperationException_DefaultTypeInfoResolverImmutable();
                }
            }
        }
 
        bool IBuiltInJsonTypeInfoResolver.IsCompatibleWithOptions(JsonSerializerOptions _)
            // Metadata generated by the default resolver is compatible by definition,
            // provided that no user extensions have been made on the class.
            => _modifiers is null or { Count: 0 } && GetType() == typeof(DefaultJsonTypeInfoResolver);
 
        internal static DefaultJsonTypeInfoResolver DefaultInstance
        {
            [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
            [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
            get
            {
                if (s_defaultInstance is DefaultJsonTypeInfoResolver result)
                {
                    return result;
                }
 
                var newInstance = new DefaultJsonTypeInfoResolver(mutable: false);
                return Interlocked.CompareExchange(ref s_defaultInstance, newInstance, comparand: null) ?? newInstance;
            }
        }
 
        private static DefaultJsonTypeInfoResolver? s_defaultInstance;
    }
}