< Summary

Information
Class: System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver
Assembly: System.Text.Json
File(s): C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\Metadata\PolymorphicTypeResolver.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 206
Coverable lines: 206
Total lines: 344
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 126
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\Metadata\PolymorphicTypeResolver.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.Concurrent;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8
 9namespace System.Text.Json.Serialization.Metadata
 10{
 11    /// <summary>
 12    /// Validates and indexes polymorphic type configuration,
 13    /// providing derived JsonTypeInfo resolution methods
 14    /// in both serialization and deserialization scenaria.
 15    /// </summary>
 16    internal sealed class PolymorphicTypeResolver
 17    {
 018        private readonly ConcurrentDictionary<Type, DerivedJsonTypeInfo?> _typeToDiscriminatorId = new();
 19        private readonly Dictionary<object, DerivedJsonTypeInfo>? _discriminatorIdtoType;
 20        private readonly JsonSerializerOptions _options;
 21
 022        public PolymorphicTypeResolver(JsonSerializerOptions options, JsonPolymorphismOptions polymorphismOptions, Type 
 023        {
 024            UnknownDerivedTypeHandling = polymorphismOptions.UnknownDerivedTypeHandling;
 025            IgnoreUnrecognizedTypeDiscriminators = polymorphismOptions.IgnoreUnrecognizedTypeDiscriminators;
 026            BaseType = baseType;
 027            _options = options;
 28
 029            if (!IsSupportedPolymorphicBaseType(BaseType))
 030            {
 031                ThrowHelper.ThrowInvalidOperationException_TypeDoesNotSupportPolymorphism(BaseType);
 32            }
 33
 034            bool containsDerivedTypes = false;
 035            foreach ((Type derivedType, object? typeDiscriminator) in polymorphismOptions.DerivedTypes)
 036            {
 037                Debug.Assert(typeDiscriminator is null or int or string);
 38
 039                if (!IsSupportedDerivedType(BaseType, derivedType) ||
 040                    (derivedType.IsAbstract && UnknownDerivedTypeHandling != JsonUnknownDerivedTypeHandling.FallBackToNe
 041                {
 042                    ThrowHelper.ThrowInvalidOperationException_DerivedTypeNotSupported(BaseType, derivedType);
 43                }
 44
 045                JsonTypeInfo derivedTypeInfo = options.GetTypeInfoInternal(derivedType);
 046                DerivedJsonTypeInfo derivedTypeInfoHolder = new(typeDiscriminator, derivedTypeInfo);
 47
 048                if (!_typeToDiscriminatorId.TryAdd(derivedType, derivedTypeInfoHolder))
 049                {
 050                    ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(BaseType, derivedType);
 51                }
 52
 053                if (typeDiscriminator is not null)
 054                {
 055                    if (!(_discriminatorIdtoType ??= new()).TryAdd(typeDiscriminator, derivedTypeInfoHolder))
 056                    {
 057                        ThrowHelper.ThrowInvalidOperationException_TypeDicriminatorIdIsAlreadySpecified(BaseType, typeDi
 58                    }
 59
 060                    UsesTypeDiscriminators = true;
 061                }
 62
 063                containsDerivedTypes = true;
 064            }
 65
 066            if (!containsDerivedTypes)
 067            {
 068                ThrowHelper.ThrowInvalidOperationException_PolymorphicTypeConfigurationDoesNotSpecifyDerivedTypes(BaseTy
 69            }
 70
 071            if (UsesTypeDiscriminators)
 072            {
 073                Debug.Assert(_discriminatorIdtoType != null, "Discriminator index must have been populated.");
 74
 075                if (!converterCanHaveMetadata)
 076                {
 077                    ThrowHelper.ThrowNotSupportedException_BaseConverterDoesNotSupportMetadata(BaseType);
 78                }
 79
 080                string propertyName = polymorphismOptions.TypeDiscriminatorPropertyName;
 081                if (!propertyName.Equals(JsonSerializer.TypePropertyName, StringComparison.Ordinal))
 082                {
 083                    byte[] utf8EncodedName = Encoding.UTF8.GetBytes(propertyName);
 84
 85                    // Check if the property name conflicts with other metadata property names
 086                    if ((JsonSerializer.GetMetadataPropertyName(utf8EncodedName, resolver: null) & ~MetadataPropertyName
 087                    {
 088                        ThrowHelper.ThrowInvalidOperationException_InvalidCustomTypeDiscriminatorPropertyName();
 89                    }
 90
 091                    CustomTypeDiscriminatorPropertyNameUtf8 = utf8EncodedName;
 092                    CustomTypeDiscriminatorPropertyNameJsonEncoded = JsonEncodedText.Encode(propertyName, options.Encode
 093                }
 94
 95                // Check if the discriminator property name conflicts with any derived property names.
 096                foreach (DerivedJsonTypeInfo derivedTypeInfo in _discriminatorIdtoType.Values)
 097                {
 098                    if (derivedTypeInfo.JsonTypeInfo.Kind is JsonTypeInfoKind.Object)
 099                    {
 0100                        foreach (JsonPropertyInfo property in derivedTypeInfo.JsonTypeInfo.Properties)
 0101                        {
 0102                            if (property is { IsIgnored: false, IsExtensionData: false } && property.Name == propertyNam
 0103                            {
 0104                                ThrowHelper.ThrowInvalidOperationException_PropertyConflictsWithMetadataPropertyName(der
 105                            }
 0106                        }
 0107                    }
 0108                }
 0109            }
 0110        }
 111
 0112        public Type BaseType { get; }
 0113        public JsonUnknownDerivedTypeHandling UnknownDerivedTypeHandling { get; }
 0114        public bool UsesTypeDiscriminators { get; }
 0115        public bool IgnoreUnrecognizedTypeDiscriminators { get; }
 0116        public byte[]? CustomTypeDiscriminatorPropertyNameUtf8 { get; }
 0117        public JsonEncodedText? CustomTypeDiscriminatorPropertyNameJsonEncoded { get; }
 118
 119        public bool TryGetDerivedJsonTypeInfo(Type runtimeType, [NotNullWhen(true)] out JsonTypeInfo? jsonTypeInfo, out 
 0120        {
 0121            Debug.Assert(BaseType.IsAssignableFrom(runtimeType));
 122
 0123            if (!_typeToDiscriminatorId.TryGetValue(runtimeType, out DerivedJsonTypeInfo? result))
 0124            {
 0125                switch (UnknownDerivedTypeHandling)
 126                {
 127                    case JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor:
 128                        // Calculate (and cache the result) of the nearest ancestor for given runtime type.
 129                        // A `null` result denotes no matching ancestor type, we also cache that.
 0130                        result = CalculateNearestAncestor(runtimeType);
 0131                        _typeToDiscriminatorId[runtimeType] = result;
 0132                        break;
 133                    case JsonUnknownDerivedTypeHandling.FallBackToBaseType:
 134                        // Recover the polymorphic contract (i.e. any type discriminators) for the base type, if it exis
 0135                        _typeToDiscriminatorId.TryGetValue(BaseType, out result);
 0136                        _typeToDiscriminatorId[runtimeType] = result;
 0137                        break;
 138
 139                    case JsonUnknownDerivedTypeHandling.FailSerialization:
 140                    default:
 0141                        if (runtimeType != BaseType)
 0142                        {
 0143                            ThrowHelper.ThrowNotSupportedException_RuntimeTypeNotSupported(BaseType, runtimeType);
 144                        }
 0145                        break;
 146                }
 0147            }
 148
 0149            if (result is null)
 0150            {
 0151                jsonTypeInfo = null;
 0152                typeDiscriminator = null;
 0153                return false;
 154            }
 155            else
 0156            {
 0157                jsonTypeInfo = result.JsonTypeInfo;
 0158                typeDiscriminator = result.TypeDiscriminator;
 0159                return true;
 160            }
 0161        }
 162
 163        public bool TryGetDerivedJsonTypeInfo(object typeDiscriminator, [NotNullWhen(true)] out JsonTypeInfo? jsonTypeIn
 0164        {
 0165            Debug.Assert(typeDiscriminator is int or string);
 0166            Debug.Assert(UsesTypeDiscriminators);
 0167            Debug.Assert(_discriminatorIdtoType != null);
 168
 0169            if (_discriminatorIdtoType.TryGetValue(typeDiscriminator, out DerivedJsonTypeInfo? result))
 0170            {
 0171                Debug.Assert(typeDiscriminator.Equals(result.TypeDiscriminator));
 0172                jsonTypeInfo = result.JsonTypeInfo;
 0173                return true;
 174            }
 175
 0176            if (!IgnoreUnrecognizedTypeDiscriminators)
 0177            {
 0178                ThrowHelper.ThrowJsonException_UnrecognizedTypeDiscriminator(typeDiscriminator);
 179            }
 180
 0181            jsonTypeInfo = null;
 0182            return false;
 0183        }
 184
 185        public static bool IsSupportedPolymorphicBaseType(Type? type) =>
 0186            type != null &&
 0187            (type.IsClass || type.IsInterface) &&
 0188            !type.IsSealed &&
 0189            !type.IsGenericTypeDefinition &&
 0190            !type.IsPointer &&
 0191            type != JsonTypeInfo.ObjectType;
 192
 193        public static bool IsSupportedDerivedType(Type baseType, Type? derivedType) =>
 0194            baseType.IsAssignableFrom(derivedType) && !derivedType.IsGenericTypeDefinition;
 195
 196        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
 197            Justification = "The call to GetInterfaces will cross-reference results with interface types " +
 198                            "already declared as derived types of the polymorphic base type.")]
 199        private DerivedJsonTypeInfo? CalculateNearestAncestor(Type type)
 0200        {
 0201            Debug.Assert(!type.IsAbstract);
 0202            Debug.Assert(BaseType.IsAssignableFrom(type));
 0203            Debug.Assert(UnknownDerivedTypeHandling == JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor);
 204
 0205            if (type == BaseType)
 0206            {
 0207                return null;
 208            }
 209
 0210            DerivedJsonTypeInfo? result = null;
 211
 212            // First, walk up the class hierarchy for any supported types.
 0213            for (Type? candidate = type.BaseType; BaseType.IsAssignableFrom(candidate); candidate = candidate.BaseType)
 0214            {
 0215                Debug.Assert(candidate != null);
 216
 0217                if (_typeToDiscriminatorId.TryGetValue(candidate, out result))
 0218                {
 0219                    break;
 220                }
 0221            }
 222
 223            // Interface hierarchies admit the possibility of diamond ambiguities in type discriminators.
 224            // Examine all interface implementations and identify potential conflicts.
 0225            if (BaseType.IsInterface)
 0226            {
 0227                foreach (Type interfaceTy in type.GetInterfaces())
 0228                {
 0229                    if (interfaceTy != BaseType && BaseType.IsAssignableFrom(interfaceTy) &&
 0230                        _typeToDiscriminatorId.TryGetValue(interfaceTy, out DerivedJsonTypeInfo? interfaceResult) &&
 0231                        interfaceResult is not null)
 0232                    {
 0233                        if (result is null)
 0234                        {
 0235                            result = interfaceResult;
 0236                        }
 237                        else
 0238                        {
 0239                            ThrowHelper.ThrowNotSupportedException_RuntimeTypeDiamondAmbiguity(BaseType, type, result.Js
 240                        }
 0241                    }
 0242                }
 0243            }
 244
 0245            return result;
 0246        }
 247
 248        /// <summary>
 249        /// Walks the type hierarchy above the current type for any types that use polymorphic configuration.
 250        /// </summary>
 251        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
 252            Justification = "The call to GetInterfaces will cross-reference results with interface types " +
 253                            "already declared as derived types of the polymorphic base type.")]
 254        internal static JsonTypeInfo? FindNearestPolymorphicBaseType(JsonTypeInfo typeInfo)
 0255        {
 0256            Debug.Assert(typeInfo.IsConfigured);
 257
 0258            if (typeInfo.PolymorphismOptions != null)
 0259            {
 260                // Type defines its own polymorphic configuration.
 0261                return null;
 262            }
 263
 0264            JsonTypeInfo? matchingResult = null;
 265
 266            // First, walk up the class hierarchy for any supported types.
 0267            for (Type? candidate = typeInfo.Type.BaseType; candidate != null; candidate = candidate.BaseType)
 0268            {
 0269                JsonTypeInfo? candidateInfo = ResolveAncestorTypeInfo(candidate, typeInfo.Options);
 0270                if (candidateInfo?.PolymorphismOptions != null)
 0271                {
 272                    // stop on the first ancestor that has a match
 0273                    matchingResult = candidateInfo;
 0274                    break;
 275                }
 0276            }
 277
 278            // Now, walk the interface hierarchy for any polymorphic interface declarations.
 0279            foreach (Type interfaceType in typeInfo.Type.GetInterfaces())
 0280            {
 0281                JsonTypeInfo? candidateInfo = ResolveAncestorTypeInfo(interfaceType, typeInfo.Options);
 0282                if (candidateInfo?.PolymorphismOptions != null)
 0283                {
 0284                    if (matchingResult != null)
 0285                    {
 286                        // Resolve any conflicting matches.
 0287                        if (matchingResult.Type.IsAssignableFrom(interfaceType))
 0288                        {
 289                            // interface is more derived than previous match, replace it.
 0290                            matchingResult = candidateInfo;
 0291                        }
 0292                        else if (interfaceType.IsAssignableFrom(matchingResult.Type))
 0293                        {
 294                            // interface is less derived than previous match, keep the previous one.
 0295                            continue;
 296                        }
 297                        else
 0298                        {
 299                            // Diamond ambiguity, do not report any ancestors.
 0300                            return null;
 301                        }
 0302                    }
 303                    else
 0304                    {
 0305                        matchingResult = candidateInfo;
 0306                    }
 0307                }
 0308            }
 309
 0310            return matchingResult;
 311
 312            static JsonTypeInfo? ResolveAncestorTypeInfo(Type type, JsonSerializerOptions options)
 0313            {
 314                try
 0315                {
 0316                    return options.GetTypeInfoInternal(type, ensureNotNull: null);
 317                }
 0318                catch
 0319                {
 320                    // The resolver produced an exception when resolving the ancestor type.
 321                    // Eat the exception and report no result instead.
 0322                    return null;
 323                }
 0324            }
 0325        }
 326
 327        /// <summary>
 328        /// JsonTypeInfo result holder for a derived type.
 329        /// </summary>
 330        private sealed class DerivedJsonTypeInfo
 331        {
 0332            public DerivedJsonTypeInfo(object? typeDiscriminator, JsonTypeInfo derivedTypeInfo)
 0333            {
 0334                Debug.Assert(typeDiscriminator is null or int or string);
 335
 0336                TypeDiscriminator = typeDiscriminator;
 0337                JsonTypeInfo = derivedTypeInfo;
 0338            }
 339
 0340            public object? TypeDiscriminator { get; }
 0341            public JsonTypeInfo JsonTypeInfo { get; }
 342        }
 343    }
 344}