< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 403
Coverable lines: 403
Total lines: 629
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 192
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%48480%
Read(...)100%110%
OnTryRead(...)0%440%
ReadFromString(...)0%660%
ReadFromObject(...)0%80800%
ThrowForMissingRequiredFields(...)0%660%
Write(...)100%110%
OnTryWrite(...)0%10100%
ConfigureJsonTypeInfo(...)100%110%
LookupCaseByName(...)0%660%
TryGetFieldIndex(...)0%660%
BuildFieldIndexMap(...)0%440%
.ctor(...)100%110%
.ctor(...)0%440%
ResolveCustomConverter(...)0%14140%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Reflection;
 9using System.Runtime.CompilerServices;
 10using System.Text.Json.Reflection;
 11using System.Text.Json.Serialization.Metadata;
 12
 13namespace System.Text.Json.Serialization.Converters
 14{
 15    // Converter for F# discriminated unions.
 16    // Fieldless cases are serialized as JSON strings.
 17    // Cases with fields are serialized as JSON objects with a type discriminator and named field properties.
 18    // The discriminator property name defaults to "$type" but can be customized via [JsonPolymorphic].
 19    internal sealed class FSharpUnionConverter<T> : JsonConverter<T>
 20    {
 021        internal override bool CanHaveMetadata => true;
 22        private readonly CaseInfo[] _casesByTag;
 23        private readonly Dictionary<string, CaseInfo> _casesByName;
 24        private readonly Dictionary<string, CaseInfo>? _casesByNameCaseInsensitive;
 25        private readonly Func<object, int> _tagReader;
 26        private readonly string _typeDiscriminatorPropertyName;
 27        private readonly JsonEncodedText _typeDiscriminatorPropertyNameEncoded;
 28        private readonly JsonUnmappedMemberHandling _effectiveUnmappedMemberHandling;
 29
 30        [RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 31        [RequiresDynamicCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 032        public FSharpUnionConverter(
 033            FSharpCoreReflectionProxy.FSharpUnionCaseInfo[] unionCases,
 034            Func<object, int> tagReader,
 035            JsonSerializerOptions options,
 036            string typeDiscriminatorPropertyName)
 037        {
 038            ConverterStrategy = ConverterStrategy.Object;
 039            SupportsMultipleTokenTypes = true;
 040            RequiresReadAhead = true;
 041            _tagReader = tagReader;
 042            _typeDiscriminatorPropertyName = typeDiscriminatorPropertyName;
 043            _typeDiscriminatorPropertyNameEncoded = JsonEncodedText.Encode(typeDiscriminatorPropertyName, options.Encode
 044            _effectiveUnmappedMemberHandling = typeof(T).GetUniqueCustomAttribute<JsonUnmappedMemberHandlingAttribute>(i
 045                ?? options.UnmappedMemberHandling;
 46
 047            _casesByTag = new CaseInfo[unionCases.Length];
 048            _casesByName = new Dictionary<string, CaseInfo>(unionCases.Length, StringComparer.Ordinal);
 49
 050            Dictionary<string, CaseInfo>? caseInsensitiveMap = options.PropertyNameCaseInsensitive
 051                ? new Dictionary<string, CaseInfo>(unionCases.Length, StringComparer.OrdinalIgnoreCase)
 052                : null;
 53
 054            JsonNamingPolicy? namingPolicy = options.PropertyNamingPolicy;
 55
 056            foreach (FSharpCoreReflectionProxy.FSharpUnionCaseInfo uc in unionCases)
 057            {
 58                // Case name resolution: JsonPropertyNameAttribute > PropertyNamingPolicy > raw name
 059                string discriminatorName = uc.JsonPropertyName
 060                    ?? namingPolicy?.ConvertName(uc.Name)
 061                    ?? uc.Name;
 62
 063                JsonEncodedText encodedDiscriminatorName = JsonEncodedText.Encode(discriminatorName, options.Encoder);
 64
 65                // Build field info for cases with fields
 066                CaseFieldInfo[]? fields = null;
 067                if (!uc.IsFieldless)
 068                {
 069                    fields = new CaseFieldInfo[uc.Fields.Length];
 070                    for (int i = 0; i < uc.Fields.Length; i++)
 071                    {
 072                        PropertyInfo prop = uc.Fields[i];
 073                        string fieldName = prop.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name
 074                            ?? namingPolicy?.ConvertName(prop.Name)
 075                            ?? prop.Name;
 076                        JsonEncodedText encodedFieldName = JsonEncodedText.Encode(fieldName, options.Encoder);
 77
 078                        fields[i] = new CaseFieldInfo(fieldName, encodedFieldName, prop, options);
 079                    }
 80
 81                    // Validate that no field name conflicts with the discriminator property name.
 082                    StringComparison conflictComparison = options.PropertyNameCaseInsensitive
 083                        ? StringComparison.OrdinalIgnoreCase
 084                        : StringComparison.Ordinal;
 85
 086                    for (int i = 0; i < fields.Length; i++)
 087                    {
 088                        if (fields[i].FieldName.Equals(typeDiscriminatorPropertyName, conflictComparison))
 089                        {
 090                            throw new InvalidOperationException(SR.Format(SR.FSharpUnionFieldConflictsWithDiscriminator,
 91                        }
 092                    }
 093                }
 94
 95                // Build default field values for deserialization with missing fields.
 096                object[]? defaultFieldValues = null;
 097                if (fields is not null)
 098                {
 099                    defaultFieldValues = new object[fields.Length];
 0100                    for (int i = 0; i < fields.Length; i++)
 0101                    {
 0102                        Type fieldType = fields[i].FieldType;
 0103                        defaultFieldValues[i] = fieldType.IsValueType ?
 0104                            RuntimeHelpers.GetUninitializedObject(fieldType) :
 0105                            null!;
 0106                    }
 0107                }
 108
 0109                var caseInfo = new CaseInfo(
 0110                    discriminatorName,
 0111                    encodedDiscriminatorName,
 0112                    uc.Tag,
 0113                    uc.IsFieldless,
 0114                    fields,
 0115                    defaultFieldValues,
 0116                    fields is not null ? BuildFieldIndexMap(fields, StringComparer.Ordinal, typeof(T), discriminatorName
 0117                    fields is not null && options.PropertyNameCaseInsensitive ? BuildFieldIndexMap(fields, StringCompare
 0118                    uc.FieldReader,
 0119                    uc.Constructor,
 0120                    typeof(T));
 121
 0122                _casesByTag[uc.Tag] = caseInfo;
 123
 0124                if (!_casesByName.TryAdd(discriminatorName, caseInfo))
 0125                {
 0126                    throw new InvalidOperationException(SR.Format(SR.FSharpUnionDuplicateCaseName, typeof(T), discrimina
 127                }
 128
 0129                if (caseInsensitiveMap is not null && !caseInsensitiveMap.TryAdd(discriminatorName, caseInfo))
 0130                {
 0131                    throw new InvalidOperationException(SR.Format(SR.FSharpUnionDuplicateCaseName, typeof(T), discrimina
 132                }
 0133            }
 134
 0135            _casesByNameCaseInsensitive = caseInsensitiveMap;
 0136        }
 137
 138        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 0139        {
 140            // Fallback for direct converter invocations. The normal pipeline
 141            // uses TryRead -> OnTryRead which forwards state automatically.
 0142            ReadStack state = default;
 0143            JsonTypeInfo jsonTypeInfo = options.GetTypeInfoInternal(typeToConvert);
 0144            state.Initialize(jsonTypeInfo);
 0145            state.Push();
 146
 0147            OnTryRead(ref reader, typeToConvert, options, ref state, out T? value);
 0148            return value!;
 0149        }
 150
 151        internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, s
 0152        {
 0153            if (reader.TokenType == JsonTokenType.String)
 0154            {
 0155                value = ReadFromString(ref reader, options);
 0156                return true;
 157            }
 158
 0159            if (reader.TokenType == JsonTokenType.StartObject)
 0160            {
 0161                value = ReadFromObject(ref reader, options, ref state);
 0162                return true;
 163            }
 164
 0165            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(T));
 166            value = default;
 167            return true;
 0168        }
 169
 170        private T ReadFromString(ref Utf8JsonReader reader, JsonSerializerOptions options)
 0171        {
 0172            string? caseName = reader.GetString();
 0173            if (caseName is null)
 0174            {
 0175                ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(T));
 176            }
 177
 0178            CaseInfo caseInfo = LookupCaseByName(caseName!);
 179
 0180            if (caseInfo.IsFieldless)
 0181            {
 0182                return (T)caseInfo.Constructor(Array.Empty<object>());
 183            }
 184
 185            // String form for a case with fields: only allowed when fields are not required.
 0186            if (options.RespectRequiredConstructorParameters)
 0187            {
 0188                ThrowHelper.ThrowJsonException();
 189            }
 190
 0191            return (T)caseInfo.Constructor((object[])caseInfo.DefaultFieldValues!.Clone());
 0192        }
 193
 194        private T ReadFromObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state)
 0195        {
 0196            Debug.Assert(reader.TokenType == JsonTokenType.StartObject);
 197
 198            // This converter opts into read-ahead buffering (via RequiresReadAhead),
 199            // so by the time ReadFromObject is called the entire JSON value has been
 200            // buffered and we can safely scan ahead for the discriminator and restore the reader position.
 0201            Utf8JsonReader checkpoint = reader;
 202
 0203            bool preserveReferences = !typeof(T).IsValueType &&
 0204                options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve;
 205
 206            // Scan for the type discriminator and any reference metadata ($id, $ref).
 0207            string? caseName = null;
 0208            string? referenceId = null;
 0209            bool hasNonMetadataProperties = false;
 0210            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
 0211            {
 0212                Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 213
 0214                if (preserveReferences)
 0215                {
 0216                    switch (JsonSerializer.GetMetadataPropertyName(reader.GetUnescapedSpan(), resolver: null))
 217                    {
 218                        case MetadataPropertyName.Ref:
 0219                            if (hasNonMetadataProperties || referenceId is not null)
 0220                            {
 0221                                ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties();
 222                            }
 223
 0224                            reader.Read();
 0225                            if (reader.TokenType != JsonTokenType.String)
 0226                            {
 0227                                ThrowHelper.ThrowJsonException();
 228                            }
 229
 0230                            string refValue = reader.GetString()!;
 231
 232                            // Validate that no other properties follow $ref.
 0233                            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
 0234                            {
 0235                                ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties();
 236                            }
 237
 0238                            return (T)state.ReferenceResolver.ResolveReference(refValue);
 239
 240                        case MetadataPropertyName.Id:
 0241                            if (referenceId is not null)
 0242                            {
 0243                                ThrowHelper.ThrowJsonException();
 244                            }
 245
 0246                            reader.Read();
 0247                            if (reader.TokenType != JsonTokenType.String)
 0248                            {
 0249                                ThrowHelper.ThrowJsonException();
 250                            }
 251
 0252                            referenceId = reader.GetString();
 0253                            continue;
 254                    }
 0255                }
 256
 0257                hasNonMetadataProperties = true;
 0258                bool isDiscriminator = reader.ValueTextEquals(_typeDiscriminatorPropertyName);
 0259                reader.Read();
 260
 0261                if (isDiscriminator)
 0262                {
 0263                    if (reader.TokenType != JsonTokenType.String)
 0264                    {
 0265                        ThrowHelper.ThrowJsonException();
 266                    }
 267
 0268                    caseName = reader.GetString();
 269
 0270                    if (!preserveReferences)
 0271                    {
 0272                        break;
 273                    }
 274
 0275                    continue;
 276                }
 277
 0278                reader.TrySkip();
 0279            }
 280
 0281            if (caseName is null)
 0282            {
 0283                throw new JsonException(SR.Format(SR.FSharpUnionMissingDiscriminatorProperty, _typeDiscriminatorProperty
 284            }
 285
 0286            CaseInfo caseInfo = LookupCaseByName(caseName);
 287
 288            // Second pass: restore reader and process all properties in a single loop.
 0289            reader = checkpoint;
 0290            bool discriminatorSeen = false;
 0291            object[]? fieldValues = caseInfo.IsFieldless ? null : (object[])caseInfo.DefaultFieldValues!.Clone();
 0292            bool trackPopulated = !caseInfo.IsFieldless && (options.RespectRequiredConstructorParameters || !options.All
 0293            BitArray? populatedFields = trackPopulated ? new BitArray(caseInfo.Fields!.Length) : null;
 294
 0295            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
 0296            {
 0297                Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 298
 299                // Skip discriminator property, detecting duplicates.
 0300                if (reader.ValueTextEquals(_typeDiscriminatorPropertyName))
 0301                {
 0302                    if (discriminatorSeen)
 0303                    {
 0304                        ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(_typeDiscriminatorPropertyName);
 305                    }
 306
 0307                    discriminatorSeen = true;
 0308                    reader.Read();
 0309                    reader.TrySkip();
 0310                    continue;
 311                }
 312
 313                // Skip metadata properties ($id, $ref) already processed in the first pass.
 0314                if (preserveReferences &&
 0315                    JsonSerializer.GetMetadataPropertyName(reader.GetUnescapedSpan(), resolver: null) is not MetadataPro
 0316                {
 0317                    reader.Read();
 0318                    reader.TrySkip();
 0319                    continue;
 320                }
 321
 322                // Try to match a union case field.
 0323                string? fieldName = reader.GetString();
 0324                reader.Read();
 325
 0326                if (fieldValues is not null && fieldName is not null && TryGetFieldIndex(fieldName, caseInfo, out int fi
 0327                {
 0328                    if (populatedFields is not null)
 0329                    {
 0330                        if (!options.AllowDuplicateProperties && populatedFields[fieldIndex])
 0331                        {
 0332                            ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(fieldName);
 333                        }
 334
 0335                        populatedFields[fieldIndex] = true;
 0336                    }
 337
 0338                    CaseFieldInfo field = caseInfo.Fields![fieldIndex];
 0339                    state.Current.JsonPropertyInfo = field.PropertyInfoForTypeInfo;
 0340                    state.Current.NumberHandling = field.NumberHandling;
 0341                    bool success = field.Converter.TryReadAsObject(ref reader, field.FieldType, options, ref state, out 
 0342                    Debug.Assert(success, "Nested converter should not suspend since the union payload is fully buffered
 0343                    fieldValues[fieldIndex] = fieldValue!;
 0344                }
 345                else
 0346                {
 0347                    if (_effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
 0348                    {
 0349                        ThrowHelper.ThrowJsonException_UnmappedJsonProperty(typeof(T), fieldName ?? string.Empty);
 350                    }
 351
 0352                    reader.TrySkip();
 0353                }
 0354            }
 355
 0356            if (options.RespectRequiredConstructorParameters && populatedFields is not null && !populatedFields.HasAllSe
 0357            {
 0358                ThrowForMissingRequiredFields(caseInfo, populatedFields);
 0359            }
 360
 0361            T result = caseInfo.IsFieldless
 0362                ? (T)caseInfo.Constructor(Array.Empty<object>())
 0363                : (T)caseInfo.Constructor(fieldValues!);
 364
 0365            if (referenceId is not null)
 0366            {
 0367                state.ReferenceResolver.AddReference(referenceId, result);
 0368            }
 369
 0370            return result;
 0371        }
 372
 373        private static void ThrowForMissingRequiredFields(CaseInfo caseInfo, BitArray populatedFields)
 0374        {
 0375            StringBuilder builder = new();
 0376            for (int i = 0; i < caseInfo.Fields!.Length; i++)
 0377            {
 0378                if (!populatedFields[i])
 0379                {
 0380                    if (!ThrowHelper.AppendMissingProperty(builder, caseInfo.Fields[i].FieldName))
 0381                    {
 0382                        break;
 383                    }
 0384                }
 0385            }
 386
 0387            ThrowHelper.ThrowJsonException_JsonRequiredPropertyMissing(caseInfo.DeclaringType, builder.ToString());
 388        }
 389
 390        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
 0391        {
 392            // Fallback for direct converter invocations. The normal pipeline
 393            // uses TryWrite -> OnTryWrite which forwards state automatically.
 0394            WriteStack state = default;
 0395            JsonTypeInfo typeInfo = options.GetTypeInfoInternal(typeof(T));
 0396            state.Initialize(typeInfo);
 0397            state.Push();
 398
 399            try
 0400            {
 0401                OnTryWrite(writer, value, options, ref state);
 0402            }
 0403            catch
 0404            {
 0405                state.DisposePendingDisposablesOnException();
 0406                throw;
 407            }
 0408        }
 409
 410        internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack 
 0411        {
 0412            int tag = _tagReader(value!);
 0413            CaseInfo caseInfo = _casesByTag[tag];
 414
 415            // Fieldless cases serialize as strings when reference tracking is not active.
 0416            if (caseInfo.IsFieldless && state.NewReferenceId is null)
 0417            {
 0418                writer.WriteStringValue(caseInfo.EncodedDiscriminatorName);
 0419                return true;
 420            }
 421
 0422            writer.WriteStartObject();
 423
 424            // Write $id metadata if a new reference was registered by TryWrite.
 0425            if (state.NewReferenceId is not null)
 0426            {
 0427                writer.WriteString(JsonSerializer.s_metadataId, state.NewReferenceId);
 0428                state.NewReferenceId = null;
 0429            }
 430
 0431            writer.WriteString(_typeDiscriminatorPropertyNameEncoded, caseInfo.EncodedDiscriminatorName);
 432
 0433            if (!caseInfo.IsFieldless)
 0434            {
 0435                object[] fieldValues = caseInfo.FieldReader(value!);
 0436                Debug.Assert(fieldValues.Length == caseInfo.Fields!.Length);
 437
 0438                for (int i = 0; i < caseInfo.Fields.Length; i++)
 0439                {
 0440                    CaseFieldInfo field = caseInfo.Fields[i];
 0441                    writer.WritePropertyName(field.EncodedFieldName);
 0442                    state.Current.JsonPropertyInfo = field.PropertyInfoForTypeInfo;
 0443                    state.Current.NumberHandling = field.NumberHandling;
 0444                    bool success = field.Converter.TryWriteAsObject(writer, fieldValues[i], options, ref state);
 0445                    Debug.Assert(success, "Nested converter should not suspend since the union payload is fully buffered
 0446                }
 0447            }
 448
 0449            writer.WriteEndObject();
 0450            return true;
 0451        }
 452
 453        internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options)
 0454        {
 455            // Clear any polymorphism options that PopulatePolymorphismMetadata() may have set
 456            // from a [JsonPolymorphic] attribute. The F# union converter handles the type
 457            // discriminator internally and does not use the standard polymorphism pipeline.
 0458            jsonTypeInfo.PolymorphismOptions = null;
 0459        }
 460
 461        private CaseInfo LookupCaseByName(string caseName)
 0462        {
 0463            if (_casesByName.TryGetValue(caseName, out CaseInfo? caseInfo))
 0464            {
 0465                return caseInfo;
 466            }
 467
 0468            if (_casesByNameCaseInsensitive?.TryGetValue(caseName, out caseInfo) == true)
 0469            {
 0470                return caseInfo;
 471            }
 472
 0473            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(T));
 474            return default!;
 0475        }
 476
 477        private static bool TryGetFieldIndex(
 478            string fieldName,
 479            CaseInfo caseInfo,
 480            out int fieldIndex)
 0481        {
 0482            if (caseInfo.FieldIndexMap!.TryGetValue(fieldName, out fieldIndex))
 0483            {
 0484                return true;
 485            }
 486
 0487            if (caseInfo.FieldIndexMapCaseInsensitive?.TryGetValue(fieldName, out fieldIndex) == true)
 0488            {
 0489                return true;
 490            }
 491
 0492            fieldIndex = -1;
 0493            return false;
 0494        }
 495
 496        private static Dictionary<string, int> BuildFieldIndexMap(CaseFieldInfo[] fields, StringComparer comparer, Type 
 0497        {
 0498            var map = new Dictionary<string, int>(fields.Length, comparer);
 0499            for (int i = 0; i < fields.Length; i++)
 0500            {
 0501                if (!map.TryAdd(fields[i].FieldName, i))
 0502                {
 0503                    throw new InvalidOperationException(SR.Format(SR.FSharpUnionDuplicateFieldName, declaringType, caseN
 504                }
 0505            }
 506
 0507            return map;
 0508        }
 509
 510        private sealed class CaseInfo
 511        {
 0512            public CaseInfo(
 0513                string discriminatorName,
 0514                JsonEncodedText encodedDiscriminatorName,
 0515                int tag,
 0516                bool isFieldless,
 0517                CaseFieldInfo[]? fields,
 0518                object[]? defaultFieldValues,
 0519                Dictionary<string, int>? fieldIndexMap,
 0520                Dictionary<string, int>? fieldIndexMapCaseInsensitive,
 0521                Func<object, object[]> fieldReader,
 0522                Func<object[], object> constructor,
 0523                Type declaringType)
 0524            {
 0525                DiscriminatorName = discriminatorName;
 0526                EncodedDiscriminatorName = encodedDiscriminatorName;
 0527                Tag = tag;
 0528                IsFieldless = isFieldless;
 0529                Fields = fields;
 0530                DefaultFieldValues = defaultFieldValues;
 0531                FieldIndexMap = fieldIndexMap;
 0532                FieldIndexMapCaseInsensitive = fieldIndexMapCaseInsensitive;
 0533                FieldReader = fieldReader;
 0534                Constructor = constructor;
 0535                DeclaringType = declaringType;
 0536            }
 537
 538            public string DiscriminatorName { get; }
 0539            public JsonEncodedText EncodedDiscriminatorName { get; }
 540            public int Tag { get; }
 0541            public bool IsFieldless { get; }
 0542            public CaseFieldInfo[]? Fields { get; }
 0543            public object[]? DefaultFieldValues { get; }
 0544            public Dictionary<string, int>? FieldIndexMap { get; }
 0545            public Dictionary<string, int>? FieldIndexMapCaseInsensitive { get; }
 0546            public Func<object, object[]> FieldReader { get; }
 0547            public Func<object[], object> Constructor { get; }
 0548            public Type DeclaringType { get; }
 549        }
 550
 551        private sealed class CaseFieldInfo
 552        {
 553            private readonly JsonSerializerOptions _options;
 554            private JsonConverter? _converter;
 555            private JsonPropertyInfo? _propertyInfoForTypeInfo;
 556
 557            [RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 558            [RequiresDynamicCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 0559            public CaseFieldInfo(
 0560                string fieldName,
 0561                JsonEncodedText encodedFieldName,
 0562                PropertyInfo propertyInfo,
 0563                JsonSerializerOptions options)
 0564            {
 0565                FieldName = fieldName;
 0566                EncodedFieldName = encodedFieldName;
 0567                FieldType = propertyInfo.PropertyType;
 0568                _options = options;
 569
 570                // Honor [JsonConverter] on the field PropertyInfo.
 0571                JsonConverterAttribute? converterAttr = propertyInfo.GetCustomAttribute<JsonConverterAttribute>(inherit:
 0572                if (converterAttr is not null)
 0573                {
 0574                    _converter = ResolveCustomConverter(converterAttr, FieldType, propertyInfo, options);
 0575                }
 576
 577                // Honor [JsonNumberHandling] on the field PropertyInfo.
 0578                NumberHandling = propertyInfo.GetCustomAttribute<JsonNumberHandlingAttribute>(inherit: false)?.Handling;
 0579            }
 580
 0581            public string FieldName { get; }
 0582            public JsonEncodedText EncodedFieldName { get; }
 0583            public Type FieldType { get; }
 0584            public JsonNumberHandling? NumberHandling { get; }
 0585            public JsonConverter Converter => _converter ??= _options.GetConverterInternal(FieldType);
 0586            public JsonPropertyInfo PropertyInfoForTypeInfo => _propertyInfoForTypeInfo ??= _options.GetTypeInfoInternal
 587
 588            [RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 589            [RequiresDynamicCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
 590            private static JsonConverter ResolveCustomConverter(JsonConverterAttribute converterAttribute, Type fieldTyp
 0591            {
 0592                Type? converterType = converterAttribute.ConverterType;
 593                JsonConverter? converter;
 594
 0595                if (converterType is null)
 0596                {
 0597                    converter = converterAttribute.CreateConverter(fieldType);
 0598                    if (converter is null)
 0599                    {
 0600                        ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(proper
 601                    }
 0602                }
 603                else
 0604                {
 0605                    ConstructorInfo? ctor = converterType.GetConstructor(Type.EmptyTypes);
 0606                    if (!typeof(JsonConverter).IsAssignableFrom(converterType) || ctor is null || !ctor.IsPublic)
 0607                    {
 0608                        ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(propertyInfo
 609                    }
 610
 0611                    converter = (JsonConverter)Activator.CreateInstance(converterType)!;
 0612                }
 613
 0614                if (!converter!.CanConvert(fieldType))
 0615                {
 0616                    ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(propertyIn
 617                }
 618
 0619                if (converter is JsonConverterFactory factory)
 0620                {
 0621                    converter = factory.GetConverterInternal(fieldType, options);
 0622                }
 623
 0624                return converter!;
 0625            }
 626        }
 627    }
 628}
 629

Methods/Properties

CanHaveMetadata()
.ctor(System.Text.Json.Serialization.Metadata.FSharpCoreReflectionProxy/FSharpUnionCaseInfo[],System.Func`2<System.Object,System.Int32>,System.Text.Json.JsonSerializerOptions,System.String)
Read(System.Text.Json.Utf8JsonReader&,System.Type,System.Text.Json.JsonSerializerOptions)
OnTryRead(System.Text.Json.Utf8JsonReader&,System.Type,System.Text.Json.JsonSerializerOptions,System.Text.Json.ReadStack&,T&)
ReadFromString(System.Text.Json.Utf8JsonReader&,System.Text.Json.JsonSerializerOptions)
ReadFromObject(System.Text.Json.Utf8JsonReader&,System.Text.Json.JsonSerializerOptions,System.Text.Json.ReadStack&)
ThrowForMissingRequiredFields(System.Text.Json.Serialization.Converters.FSharpUnionConverter`1/CaseInfo<T>,System.Collections.BitArray)
Write(System.Text.Json.Utf8JsonWriter,T,System.Text.Json.JsonSerializerOptions)
OnTryWrite(System.Text.Json.Utf8JsonWriter,T,System.Text.Json.JsonSerializerOptions,System.Text.Json.WriteStack&)
ConfigureJsonTypeInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo,System.Text.Json.JsonSerializerOptions)
LookupCaseByName(System.String)
TryGetFieldIndex(System.String,System.Text.Json.Serialization.Converters.FSharpUnionConverter`1/CaseInfo<T>,System.Int32&)
BuildFieldIndexMap(System.Text.Json.Serialization.Converters.FSharpUnionConverter`1/CaseFieldInfo<T>[],System.StringComparer,System.Type,System.String)
.ctor(System.String,System.Text.Json.JsonEncodedText,System.Int32,System.Boolean,System.Text.Json.Serialization.Converters.FSharpUnionConverter`1/CaseFieldInfo<T>[],System.Object[],System.Collections.Generic.Dictionary`2<System.String,System.Int32>,System.Collections.Generic.Dictionary`2<System.String,System.Int32>,System.Func`2<System.Object,System.Object[]>,System.Func`2<System.Object[],System.Object>,System.Type)
EncodedDiscriminatorName()
IsFieldless()
Fields()
DefaultFieldValues()
FieldIndexMap()
FieldIndexMapCaseInsensitive()
FieldReader()
Constructor()
DeclaringType()
.ctor(System.String,System.Text.Json.JsonEncodedText,System.Reflection.PropertyInfo,System.Text.Json.JsonSerializerOptions)
FieldName()
EncodedFieldName()
FieldType()
NumberHandling()
Converter()
PropertyInfoForTypeInfo()
ResolveCustomConverter(System.Text.Json.Serialization.JsonConverterAttribute,System.Type,System.Reflection.PropertyInfo,System.Text.Json.JsonSerializerOptions)