File: System\Text\Json\Serialization\Metadata\JsonMetadataServices.Helpers.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.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization.Converters;
 
namespace System.Text.Json.Serialization.Metadata
{
    public static partial class JsonMetadataServices
    {
        /// <summary>
        /// Creates serialization metadata for a type using a simple converter.
        /// </summary>
        private static JsonTypeInfo<T> CreateCore<T>(JsonConverter converter, JsonSerializerOptions options)
        {
            var typeInfo = new JsonTypeInfo<T>(converter, options);
            typeInfo.PopulatePolymorphismMetadata();
            typeInfo.MapInterfaceTypesToCallbacks();
 
            // Plug in any converter configuration -- should be run last.
            converter.ConfigureJsonTypeInfo(typeInfo, options);
            typeInfo.IsCustomized = false;
            return typeInfo;
        }
 
        /// <summary>
        /// Creates serialization metadata for an object.
        /// </summary>
        private static JsonTypeInfo<T> CreateCore<T>(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo)
        {
            JsonConverter<T> converter = GetConverter(objectInfo);
            var typeInfo = new JsonTypeInfo<T>(converter, options);
            if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
            {
                // NB parameter metadata must be populated *before* property metadata
                // so that properties can be linked to their associated parameters.
                typeInfo.CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator;
                PopulateParameterInfoValues(typeInfo, objectInfo.ConstructorParameterMetadataInitializer);
            }
            else
            {
                typeInfo.SetCreateObjectIfCompatible(objectInfo.ObjectCreator);
                typeInfo.CreateObjectForExtensionDataProperty = ((JsonTypeInfo)typeInfo).CreateObject;
            }
 
            if (objectInfo.PropertyMetadataInitializer != null)
            {
                typeInfo.SourceGenDelayedPropertyInitializer = objectInfo.PropertyMetadataInitializer;
            }
            else
            {
                typeInfo.PropertyMetadataSerializationNotSupported = true;
            }
 
            typeInfo.ConstructorAttributeProviderFactory = objectInfo.ConstructorAttributeProviderFactory;
            typeInfo.SerializeHandler = objectInfo.SerializeHandler;
            typeInfo.NumberHandling = objectInfo.NumberHandling;
            typeInfo.PopulatePolymorphismMetadata();
            typeInfo.MapInterfaceTypesToCallbacks();
 
            // Plug in any converter configuration -- should be run last.
            converter.ConfigureJsonTypeInfo(typeInfo, options);
            typeInfo.IsCustomized = false;
            return typeInfo;
        }
 
        /// <summary>
        /// Creates serialization metadata for a collection.
        /// </summary>
        private static JsonTypeInfo<T> CreateCore<T>(
            JsonSerializerOptions options,
            JsonCollectionInfoValues<T> collectionInfo,
            JsonConverter<T> converter,
            object? createObjectWithArgs = null,
            object? addFunc = null)
        {
            if (collectionInfo is null)
            {
                ThrowHelper.ThrowArgumentNullException(nameof(collectionInfo));
            }
 
            converter = collectionInfo.SerializeHandler != null
                ? new JsonMetadataServicesConverter<T>(converter)
                : converter;
 
            JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
 
            typeInfo.KeyTypeInfo = collectionInfo.KeyInfo;
            typeInfo.ElementTypeInfo = collectionInfo.ElementInfo;
            Debug.Assert(typeInfo.Kind != JsonTypeInfoKind.None);
            typeInfo.NumberHandling = collectionInfo.NumberHandling;
            typeInfo.SerializeHandler = collectionInfo.SerializeHandler;
            typeInfo.CreateObjectWithArgs = createObjectWithArgs;
            typeInfo.AddMethodDelegate = addFunc;
            typeInfo.SetCreateObjectIfCompatible(collectionInfo.ObjectCreator);
            typeInfo.PopulatePolymorphismMetadata();
            typeInfo.MapInterfaceTypesToCallbacks();
 
            // Plug in any converter configuration -- should be run last.
            converter.ConfigureJsonTypeInfo(typeInfo, options);
            typeInfo.IsCustomized = false;
            return typeInfo;
        }
 
        private static JsonConverter<T> GetConverter<T>(JsonObjectInfoValues<T> objectInfo)
        {
#pragma warning disable CS8714 // Nullability of type argument 'T' doesn't match 'notnull' constraint.
            JsonConverter<T> converter = objectInfo.ObjectWithParameterizedConstructorCreator != null
                ? new LargeObjectWithParameterizedConstructorConverter<T>()
                : new ObjectDefaultConverter<T>();
#pragma warning restore CS8714
 
            return objectInfo.SerializeHandler != null
                ? new JsonMetadataServicesConverter<T>(converter)
                : converter;
        }
 
        private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo, Func<JsonParameterInfoValues[]?>? paramFactory)
        {
            Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
            Debug.Assert(!typeInfo.IsReadOnly);
 
            if (paramFactory?.Invoke() is JsonParameterInfoValues[] parameterInfoValues)
            {
                typeInfo.PopulateParameterInfoValues(parameterInfoValues);
            }
            else
            {
                typeInfo.PropertyMetadataSerializationNotSupported = true;
            }
        }
 
        internal static void PopulateProperties(JsonTypeInfo typeInfo, JsonTypeInfo.JsonPropertyInfoList propertyList, Func<JsonSerializerContext, JsonPropertyInfo[]> propInitFunc)
        {
            Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
            Debug.Assert(!typeInfo.IsConfigured);
            Debug.Assert(typeInfo.Type != JsonTypeInfo.ObjectType);
            Debug.Assert(typeInfo.Converter.ElementType is null);
 
            JsonSerializerContext? context = typeInfo.Options.TypeInfoResolver as JsonSerializerContext;
            JsonPropertyInfo[] properties = propInitFunc(context!);
 
            // Regardless of the source generator we need to re-run the naming conflict resolution algorithm
            // at run time since it is possible that the naming policy or other configs can be different then.
            JsonTypeInfo.PropertyHierarchyResolutionState state = new(typeInfo.Options);
 
            foreach (JsonPropertyInfo jsonPropertyInfo in properties)
            {
                if (!jsonPropertyInfo.SrcGen_IsPublic)
                {
                    if (jsonPropertyInfo.SrcGen_HasJsonInclude)
                    {
                        Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen");
                        ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnInaccessibleProperty(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType);
                    }
 
                    continue;
                }
 
                if (jsonPropertyInfo.MemberType == MemberTypes.Field && !jsonPropertyInfo.SrcGen_HasJsonInclude && !typeInfo.Options.IncludeFields)
                {
                    continue;
                }
 
                propertyList.AddPropertyWithConflictResolution(jsonPropertyInfo, ref state);
            }
 
            if (state.IsPropertyOrderSpecified)
            {
                propertyList.SortProperties();
            }
        }
 
        private static JsonPropertyInfo<T> CreatePropertyInfoCore<T>(JsonPropertyInfoValues<T> propertyInfoValues, JsonSerializerOptions options)
        {
            var propertyInfo = new JsonPropertyInfo<T>(propertyInfoValues.DeclaringType, declaringTypeInfo: null, options);
 
            DeterminePropertyName(propertyInfo,
                declaredPropertyName: propertyInfoValues.PropertyName,
                declaredJsonPropertyName: propertyInfoValues.JsonPropertyName);
 
            propertyInfo.MemberName = propertyInfoValues.PropertyName;
            propertyInfo.MemberType = propertyInfoValues.IsProperty ? MemberTypes.Property : MemberTypes.Field;
            propertyInfo.SrcGen_IsPublic = propertyInfoValues.IsPublic;
            propertyInfo.SrcGen_HasJsonInclude = propertyInfoValues.HasJsonInclude;
            propertyInfo.IsExtensionData = propertyInfoValues.IsExtensionData;
            propertyInfo.CustomConverter = propertyInfoValues.Converter;
 
            if (propertyInfo.IgnoreCondition != JsonIgnoreCondition.Always)
            {
                propertyInfo.Get = propertyInfoValues.Getter!;
                propertyInfo.Set = propertyInfoValues.Setter;
            }
 
            propertyInfo.IgnoreCondition = propertyInfoValues.IgnoreCondition;
            propertyInfo.JsonTypeInfo = propertyInfoValues.PropertyTypeInfo;
            propertyInfo.NumberHandling = propertyInfoValues.NumberHandling;
            propertyInfo.AttributeProviderFactory = propertyInfoValues.AttributeProviderFactory;
 
            return propertyInfo;
        }
 
        private static void DeterminePropertyName(
            JsonPropertyInfo propertyInfo,
            string declaredPropertyName,
            string? declaredJsonPropertyName)
        {
            string? name;
 
            // Property name settings.
            if (declaredJsonPropertyName != null)
            {
                name = declaredJsonPropertyName;
            }
            else if (propertyInfo.Options.PropertyNamingPolicy == null)
            {
                name = declaredPropertyName;
            }
            else
            {
                name = propertyInfo.Options.PropertyNamingPolicy.ConvertName(declaredPropertyName);
            }
 
            // Compat: We need to do validation before we assign Name so that we get InvalidOperationException rather than ArgumentNullException
            if (name == null)
            {
                ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(propertyInfo);
            }
 
            propertyInfo.Name = name;
        }
    }
}