File: System\Text\Json\Serialization\Converters\FSharp\FSharpTypeConverterFactory.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.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json.Serialization.Metadata;
using FSharpKind = System.Text.Json.Serialization.Metadata.FSharpCoreReflectionProxy.FSharpKind;
 
namespace System.Text.Json.Serialization.Converters
{
    [RequiresDynamicCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
    internal sealed class FSharpTypeConverterFactory : JsonConverterFactory
    {
        [RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
        public FSharpTypeConverterFactory() { }
 
        private ObjectConverterFactory? _recordConverterFactory;
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The ctor is marked RequiresUnreferencedCode.")]
        public override bool CanConvert(Type typeToConvert) =>
            FSharpCoreReflectionProxy.IsFSharpType(typeToConvert) &&
                FSharpCoreReflectionProxy.Instance.DetectFSharpKind(typeToConvert) is not FSharpKind.Unrecognized;
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The ctor is marked RequiresUnreferencedCode.")]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType",
            Justification = "The ctor is marked RequiresUnreferencedCode.")]
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            Debug.Assert(CanConvert(typeToConvert));
 
            Type elementType;
            Type converterFactoryType;
            object?[]? constructorArguments = null;
 
            switch (FSharpCoreReflectionProxy.Instance.DetectFSharpKind(typeToConvert))
            {
                case FSharpKind.Option:
                    elementType = typeToConvert.GetGenericArguments()[0];
                    converterFactoryType = typeof(FSharpOptionConverter<,>).MakeGenericType(typeToConvert, elementType);
                    constructorArguments = new object[] { options.GetConverterInternal(elementType) };
                    break;
                case FSharpKind.ValueOption:
                    elementType = typeToConvert.GetGenericArguments()[0];
                    converterFactoryType = typeof(FSharpValueOptionConverter<,>).MakeGenericType(typeToConvert, elementType);
                    constructorArguments = new object[] { options.GetConverterInternal(elementType) };
                    break;
                case FSharpKind.List:
                    elementType = typeToConvert.GetGenericArguments()[0];
                    converterFactoryType = typeof(FSharpListConverter<,>).MakeGenericType(typeToConvert, elementType);
                    break;
                case FSharpKind.Set:
                    elementType = typeToConvert.GetGenericArguments()[0];
                    converterFactoryType = typeof(FSharpSetConverter<,>).MakeGenericType(typeToConvert, elementType);
                    break;
                case FSharpKind.Map:
                    Type[] genericArgs = typeToConvert.GetGenericArguments();
                    Type keyType = genericArgs[0];
                    Type valueType = genericArgs[1];
                    converterFactoryType = typeof(FSharpMapConverter<,,>).MakeGenericType(typeToConvert, keyType, valueType);
                    break;
                case FSharpKind.Record:
                    // Use a modified object converter factory that picks the right constructor for struct record deserialization.
                    ObjectConverterFactory objectFactory = _recordConverterFactory ??= new ObjectConverterFactory(useDefaultConstructorInUnannotatedStructs: false);
                    Debug.Assert(objectFactory.CanConvert(typeToConvert));
                    return objectFactory.CreateConverter(typeToConvert, options);
                case FSharpKind.Union:
                    return CreateFSharpUnionConverter(typeToConvert, options);
                default:
                    Debug.Fail("Unrecognized F# type.");
                    throw new Exception();
            }
 
            return (JsonConverter)Activator.CreateInstance(converterFactoryType, constructorArguments)!;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The ctor is marked RequiresUnreferencedCode.")]
        private static JsonConverter CreateFSharpUnionConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            FSharpCoreReflectionProxy proxy = FSharpCoreReflectionProxy.Instance;
            FSharpCoreReflectionProxy.FSharpUnionCaseInfo[] caseInfos = proxy.GetUnionCaseInfos(typeToConvert);
            Func<object, int> tagReader = proxy.CreateUnionTagReader(typeToConvert);
 
            // Read [JsonPolymorphic] for TypeDiscriminatorPropertyName customization.
            string typeDiscriminatorPropertyName = typeToConvert
                .GetCustomAttribute<JsonPolymorphicAttribute>(inherit: false)?.TypeDiscriminatorPropertyName
                ?? JsonSerializer.TypePropertyName;
 
            // Validate the discriminator property name doesn't conflict with reserved metadata properties ($id, $ref, $values).
            if (!typeDiscriminatorPropertyName.Equals(JsonSerializer.TypePropertyName, StringComparison.Ordinal))
            {
                byte[] utf8EncodedName = System.Text.Encoding.UTF8.GetBytes(typeDiscriminatorPropertyName);
                if ((JsonSerializer.GetMetadataPropertyName(utf8EncodedName, resolver: null) & ~MetadataPropertyName.Type) != 0)
                {
                    ThrowHelper.ThrowInvalidOperationException_InvalidCustomTypeDiscriminatorPropertyName();
                }
            }
 
            Type converterType = typeof(FSharpUnionConverter<>).MakeGenericType(typeToConvert);
            return (JsonConverter)Activator.CreateInstance(converterType, new object[] { caseInfos, tagReader, options, typeDiscriminatorPropertyName })!;
        }
    }
}