|
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json.Reflection;
namespace System.Text.Json.Serialization.Metadata
{
public partial class DefaultJsonTypeInfoResolver
{
internal static MemberAccessor MemberAccessor
{
[RequiresUnreferencedCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
get
{
return global::System.Text.Json.Serialization.Metadata.MemberAccessor.Instance;
}
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
{
JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, converter, options);
if (GetNumberHandlingForType(typeInfo.Type) is { } numberHandling)
{
typeInfo.NumberHandling = numberHandling;
}
if (GetObjectCreationHandlingForType(typeInfo.Type) is { } creationHandling)
{
typeInfo.PreferredPropertyObjectCreationHandling = creationHandling;
}
if (GetUnmappedMemberHandling(typeInfo.Type) is { } unmappedMemberHandling
&& typeInfo.Kind is JsonTypeInfoKind.Object)
{
typeInfo.UnmappedMemberHandling = unmappedMemberHandling;
}
PopulatePolymorphismMetadata(typeInfo);
typeInfo.MapInterfaceTypesToCallbacks();
Func<object>? createObject = DetermineCreateObjectDelegate(type, converter);
typeInfo.SetCreateObjectIfCompatible(createObject);
typeInfo.CreateObjectForExtensionDataProperty = createObject;
if (typeInfo is { Kind: JsonTypeInfoKind.Object, IsNullable: false })
{
NullabilityInfoContext nullabilityCtx = new();
if (converter.ConstructorIsParameterized)
{
// NB parameter metadata must be populated *before* property metadata
// so that properties can be linked to their associated parameters.
PopulateParameterInfoValues(typeInfo, nullabilityCtx);
}
PopulateProperties(typeInfo, nullabilityCtx);
typeInfo.ConstructorAttributeProvider = typeInfo.Converter.ConstructorInfo;
}
if (typeInfo.Kind is JsonTypeInfoKind.Union)
{
PopulateUnionMetadata(typeInfo);
}
// Plug in any converter configuration -- should be run last.
converter.ConfigureJsonTypeInfo(typeInfo, options);
converter.ConfigureJsonTypeInfoUsingReflection(typeInfo, options);
return typeInfo;
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
internal static void PopulatePolymorphismMetadata(JsonTypeInfo typeInfo)
{
Debug.Assert(!typeInfo.IsReadOnly);
JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(typeInfo.Type, out JsonPolymorphicAttribute? polymorphicAttribute);
if (options is not null)
{
ResolveOpenGenericDerivedTypes(typeInfo.Type, options.DerivedTypes);
typeInfo.SetPolymorphismOptions(options);
}
if (typeInfo.Kind is not JsonTypeInfoKind.Union)
{
if (polymorphicAttribute?.TypeClassifier is Type classifierFactoryType)
{
if (!typeof(JsonTypeClassifierFactory).IsAssignableFrom(classifierFactoryType))
{
ThrowHelper.ThrowInvalidOperationException_TypeClassifierMustDeriveFromJsonTypeClassifierFactory(classifierFactoryType, typeInfo.Type);
}
typeInfo.TypeClassifierFactory = (JsonTypeClassifierFactory)Activator.CreateInstance(classifierFactoryType)!;
}
if (typeInfo.PolymorphismOptions is not null &&
(typeInfo.TypeClassifierFactory is not null || typeInfo.Options.TypeClassifiers.Count > 0))
{
typeInfo.TypeClassifierResolutionPending = true;
}
}
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void ResolveOpenGenericDerivedTypes(Type baseType, IList<JsonDerivedType> derivedTypes)
{
Type? baseTypeDefinition = null;
Type[]? baseTypeArgs = null;
for (int i = 0; i < derivedTypes.Count; i++)
{
JsonDerivedType entry = derivedTypes[i];
if (entry.DerivedType is null || !entry.DerivedType.IsGenericTypeDefinition)
{
// entry.DerivedType is annotated non-nullable, but the public
// JsonDerivedType constructors do not validate the argument, so a
// [JsonDerivedType(derivedType: null)] attribute (or an explicit
// null) yields an entry with DerivedType == null. The downstream
// validation in PolymorphicTypeResolver will surface a friendly
// diagnostic; skip silently here so the explicit error wins.
continue;
}
if (!baseType.IsGenericType)
{
ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeCouldNotBeResolved(
baseType, entry.DerivedType, SR.Polymorphism_OpenGeneric_Reason_NotAssignable);
}
baseTypeDefinition ??= baseType.GetGenericTypeDefinition();
baseTypeArgs ??= baseType.GetGenericArguments();
if (!TryResolveOpenGenericDerivedType(
entry.DerivedType, baseTypeDefinition, baseTypeArgs,
out Type? resolvedType, out string? failureReason))
{
ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeCouldNotBeResolved(
baseType, entry.DerivedType, failureReason!);
}
derivedTypes[i] = new JsonDerivedType(resolvedType!, entry.TypeDiscriminator);
}
}
/// <summary>
/// Reflection-side resolver: closes <paramref name="openDerivedType"/> against the
/// constructed base type identified by (<paramref name="baseTypeDefinition"/>,
/// <paramref name="baseTypeArgs"/>) via structural unification.
///
/// IMPORTANT: This implementation MIRRORS the source-gen resolver
/// <c>JsonSourceGenerator.Parser.TryResolveOpenGenericDerivedType</c> in
/// gen/JsonSourceGenerator.Parser.cs. Both implementations -- the structural
/// unbound pre-check, the per-ancestor unification, and the ambiguity
/// detection -- must be kept in lockstep so that reflection and source-gen
/// produce the same closed type for the same registration. Any algorithmic
/// change here must be applied in the source-gen mirror as well.
///
/// Known intentional asymmetry with the source-gen mirror: source-gen rejects a
/// managed value type (e.g. a struct containing reference fields) supplied for a
/// <c>where T : unmanaged</c> constraint because emitting such a closed type would
/// produce a C# compile error (CS8377). The reflection resolver, by contrast,
/// delegates constraint validation to <see cref="Type.MakeGenericType"/>, which only
/// enforces the underlying value-type part of the constraint at runtime (the
/// <c>unmanaged</c> modreq is not surfaced through standard reflection metadata).
/// As a result, reflection accepts managed structs for <c>unmanaged</c>-constrained
/// derived types where source-gen rejects them. This divergence cannot be bridged
/// without emitting invalid C# code on the source-gen side.
/// </summary>
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static bool TryResolveOpenGenericDerivedType(
Type openDerivedType,
Type baseTypeDefinition,
Type[] baseTypeArgs,
out Type? closedDerivedType,
out string? failureReason)
{
closedDerivedType = null;
failureReason = null;
// Find every ancestor of the open derived type whose generic type definition matches
// the base type definition. For classes there is at most one such ancestor, but for
// interfaces a derived type can implement the same interface definition multiple times
// with different type arguments (e.g. Derived<T> : IBase<T>, IBase<List<T>>).
List<Type> matchingBases = new();
foreach (Type match in openDerivedType.GetMatchingGenericBaseTypes(baseTypeDefinition))
{
matchingBases.Add(match);
}
if (matchingBases.Count == 0)
{
failureReason = SR.Polymorphism_OpenGeneric_Reason_NotAssignable;
return false;
}
// The full set of generic parameters we must bind includes the parameters of the
// derived type itself plus any parameters declared by enclosing generic types
// (e.g. Outer<T>.Derived needs T bound from the outer class).
// Type.GetGenericArguments() on an open generic type returns this complete set.
Type[] requiredParams = openDerivedType.GetGenericArguments();
// Structural unbound pre-check: every required parameter must appear at least once
// somewhere in some matching ancestor's type arguments. If a parameter never appears
// at all, no closed base could ever bind it -- the derived definition is malformed
// regardless of which closed base it is registered against.
HashSet<Type> referencedParams = new();
foreach (Type mb in matchingBases)
{
foreach (Type arg in mb.GetGenericArguments())
{
CollectReferencedParameters(arg, referencedParams);
}
}
foreach (Type required in requiredParams)
{
if (!referencedParams.Contains(required))
{
failureReason = SR.Format(SR.Polymorphism_OpenGeneric_Reason_UnboundParameter, required.Name);
return false;
}
}
Type[]? successfulArgs = null;
int successCount = 0;
foreach (Type matchingBase in matchingBases)
{
Type[] matchingBaseArgs = matchingBase.GetGenericArguments();
Debug.Assert(matchingBaseArgs.Length == baseTypeArgs.Length,
"matchingBase and baseTypeArgs share the same generic type definition, so arity must match.");
var substitution = new Dictionary<Type, Type>(requiredParams.Length);
bool unified = true;
for (int i = 0; i < matchingBaseArgs.Length; i++)
{
if (!matchingBaseArgs[i].TryUnifyWith(baseTypeArgs[i], substitution))
{
unified = false;
break;
}
}
if (!unified)
{
continue;
}
// Unification succeeded for every position. Every required parameter of the
// derived type definition must be bound by this ancestor; otherwise the
// resulting closed type would have unbound type arguments (an unspeakable type).
// A sibling ancestor may still bind this parameter, so failure here is not fatal.
Type[] closedArgs = new Type[requiredParams.Length];
bool allBound = true;
for (int i = 0; i < requiredParams.Length; i++)
{
if (!substitution.TryGetValue(requiredParams[i], out Type? boundArg))
{
allBound = false;
break;
}
closedArgs[i] = boundArg;
}
if (!allBound)
{
continue;
}
successCount++;
if (successCount == 1)
{
successfulArgs = closedArgs;
}
else
{
failureReason = SR.Polymorphism_OpenGeneric_Reason_AmbiguousMatch;
return false;
}
}
if (successCount == 0 || successfulArgs is null)
{
failureReason = SR.Polymorphism_OpenGeneric_Reason_UnificationFailed;
return false;
}
try
{
closedDerivedType = openDerivedType.MakeGenericType(successfulArgs);
return true;
}
catch (Exception ex) when (ex is ArgumentException or TypeLoadException)
{
// Constraint violation or load failure (e.g. unmanaged constraint, which is
// not observable via the standard reflection constraint metadata). We use a
// structured reason rather than ex.Message so that the outer template — which
// appends its own trailing period — never produces a double period.
failureReason = SR.Polymorphism_OpenGeneric_Reason_ConstraintViolation;
return false;
}
}
private static void CollectReferencedParameters(Type pattern, HashSet<Type> set)
{
if (pattern.IsGenericParameter)
{
set.Add(pattern);
return;
}
if (pattern.HasElementType)
{
CollectReferencedParameters(pattern.GetElementType()!, set);
return;
}
if (pattern.IsGenericType)
{
foreach (Type arg in pattern.GetGenericArguments())
{
CollectReferencedParameters(arg, set);
}
}
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void PopulateProperties(JsonTypeInfo typeInfo, NullabilityInfoContext nullabilityCtx)
{
Debug.Assert(!typeInfo.IsReadOnly);
Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
// SetsRequiredMembersAttribute means that all required members are assigned by constructor and therefore there is no enforcement
bool constructorHasSetsRequiredMembersAttribute =
typeInfo.Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false;
// Resolve the type-level JsonNamingPolicyAttribute once for the entire type.
JsonNamingPolicy? typeNamingPolicy = typeInfo.Type.GetUniqueCustomAttribute<JsonNamingPolicyAttribute>(inherit: false)?.NamingPolicy;
// Resolve type-level [JsonIgnore] once per type, rather than per-member.
JsonIgnoreCondition? typeIgnoreCondition = typeInfo.Type.GetUniqueCustomAttribute<JsonIgnoreAttribute>(inherit: false)?.Condition;
if (typeIgnoreCondition == JsonIgnoreCondition.Always)
{
ThrowHelper.ThrowInvalidOperationException(SR.DefaultIgnoreConditionInvalid);
}
JsonTypeInfo.PropertyHierarchyResolutionState state = new(typeInfo.Options);
// Walk the type hierarchy starting from the current type up to the base type(s)
foreach (Type currentType in typeInfo.Type.GetSortedTypeHierarchy())
{
if (currentType == JsonTypeInfo.ObjectType ||
currentType == typeof(ValueType))
{
// Don't process any members for typeof(object) or System.ValueType
break;
}
AddMembersDeclaredBySuperType(
typeInfo,
currentType,
typeNamingPolicy,
nullabilityCtx,
typeIgnoreCondition,
constructorHasSetsRequiredMembersAttribute,
ref state);
}
if (state.IsPropertyOrderSpecified)
{
typeInfo.PropertyList.SortProperties();
}
}
private const BindingFlags AllInstanceMembers =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly;
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void AddMembersDeclaredBySuperType(
JsonTypeInfo typeInfo,
Type currentType,
JsonNamingPolicy? typeNamingPolicy,
NullabilityInfoContext nullabilityCtx,
JsonIgnoreCondition? typeIgnoreCondition,
bool constructorHasSetsRequiredMembersAttribute,
ref JsonTypeInfo.PropertyHierarchyResolutionState state)
{
Debug.Assert(!typeInfo.IsReadOnly);
Debug.Assert(currentType.IsAssignableFrom(typeInfo.Type));
// Compiler adds RequiredMemberAttribute to type if any of the members are marked with 'required' keyword.
bool shouldCheckMembersForRequiredMemberAttribute =
!constructorHasSetsRequiredMembersAttribute && currentType.HasRequiredMemberAttribute();
foreach (PropertyInfo propertyInfo in currentType.GetProperties(AllInstanceMembers))
{
// Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
if (propertyInfo.GetIndexParameters().Length > 0 ||
PropertyIsOverriddenAndIgnored(propertyInfo, state.IgnoredProperties))
{
continue;
}
bool hasJsonIncludeAttribute = propertyInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) != null;
// Only include properties that either have a public getter or a public setter or have the JsonIncludeAttribute set.
if (propertyInfo.GetMethod?.IsPublic == true ||
propertyInfo.SetMethod?.IsPublic == true ||
hasJsonIncludeAttribute)
{
AddMember(
typeInfo,
typeToConvert: propertyInfo.PropertyType,
memberInfo: propertyInfo,
typeNamingPolicy,
nullabilityCtx,
typeIgnoreCondition,
shouldCheckMembersForRequiredMemberAttribute,
hasJsonIncludeAttribute,
ref state);
}
}
foreach (FieldInfo fieldInfo in currentType.GetFields(AllInstanceMembers))
{
bool hasJsonIncludeAttribute = fieldInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) != null;
if (hasJsonIncludeAttribute || (fieldInfo.IsPublic && typeInfo.Options.IncludeFields))
{
AddMember(
typeInfo,
typeToConvert: fieldInfo.FieldType,
memberInfo: fieldInfo,
typeNamingPolicy,
nullabilityCtx,
typeIgnoreCondition,
shouldCheckMembersForRequiredMemberAttribute,
hasJsonIncludeAttribute,
ref state);
}
}
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void AddMember(
JsonTypeInfo typeInfo,
Type typeToConvert,
MemberInfo memberInfo,
JsonNamingPolicy? typeNamingPolicy,
NullabilityInfoContext nullabilityCtx,
JsonIgnoreCondition? typeIgnoreCondition,
bool shouldCheckForRequiredKeyword,
bool hasJsonIncludeAttribute,
ref JsonTypeInfo.PropertyHierarchyResolutionState state)
{
JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeNamingPolicy, nullabilityCtx, typeIgnoreCondition, typeInfo.Options, shouldCheckForRequiredKeyword, hasJsonIncludeAttribute);
if (jsonPropertyInfo == null)
{
// ignored invalid property
return;
}
Debug.Assert(jsonPropertyInfo.Name != null);
typeInfo.PropertyList.AddPropertyWithConflictResolution(jsonPropertyInfo, ref state);
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static JsonPropertyInfo? CreatePropertyInfo(
JsonTypeInfo typeInfo,
Type typeToConvert,
MemberInfo memberInfo,
JsonNamingPolicy? typeNamingPolicy,
NullabilityInfoContext nullabilityCtx,
JsonIgnoreCondition? typeIgnoreCondition,
JsonSerializerOptions options,
bool shouldCheckForRequiredKeyword,
bool hasJsonIncludeAttribute)
{
JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute<JsonIgnoreAttribute>(inherit: false)?.Condition;
// Fall back to the type-level [JsonIgnore] if no member-level attribute is specified.
if (ignoreCondition is null && typeIgnoreCondition is not null)
{
// WhenWritingNull is invalid for non-nullable value types; treat as Never in that case
// so that the type-level annotation still overrides the global JSO DefaultIgnoreCondition.
ignoreCondition = typeIgnoreCondition == JsonIgnoreCondition.WhenWritingNull && !typeToConvert.IsNullableType()
? JsonIgnoreCondition.Never
: typeIgnoreCondition;
}
if (JsonTypeInfo.IsInvalidForSerialization(typeToConvert))
{
if (ignoreCondition == JsonIgnoreCondition.Always)
return null;
ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(typeToConvert, memberInfo.DeclaringType, memberInfo);
}
// Resolve any custom converters on the attribute level.
JsonConverter? customConverter;
try
{
customConverter = GetCustomConverterForMember(typeToConvert, memberInfo, options);
}
catch (InvalidOperationException) when (ignoreCondition == JsonIgnoreCondition.Always)
{
// skip property altogether if attribute is invalid and the property is ignored
return null;
}
JsonPropertyInfo jsonPropertyInfo = typeInfo.CreatePropertyUsingReflection(typeToConvert, declaringType: memberInfo.DeclaringType);
PopulatePropertyInfo(jsonPropertyInfo, memberInfo, customConverter, ignoreCondition, nullabilityCtx, shouldCheckForRequiredKeyword, hasJsonIncludeAttribute, typeNamingPolicy);
return jsonPropertyInfo;
}
private static JsonNumberHandling? GetNumberHandlingForType(Type type)
{
JsonNumberHandlingAttribute? numberHandlingAttribute = type.GetUniqueCustomAttribute<JsonNumberHandlingAttribute>(inherit: false);
return numberHandlingAttribute?.Handling;
}
private static JsonObjectCreationHandling? GetObjectCreationHandlingForType(Type type)
{
JsonObjectCreationHandlingAttribute? creationHandlingAttribute = type.GetUniqueCustomAttribute<JsonObjectCreationHandlingAttribute>(inherit: false);
return creationHandlingAttribute?.Handling;
}
private static JsonUnmappedMemberHandling? GetUnmappedMemberHandling(Type type)
{
JsonUnmappedMemberHandlingAttribute? numberHandlingAttribute = type.GetUniqueCustomAttribute<JsonUnmappedMemberHandlingAttribute>(inherit: false);
return numberHandlingAttribute?.UnmappedMemberHandling;
}
private static bool PropertyIsOverriddenAndIgnored(PropertyInfo propertyInfo, Dictionary<string, JsonPropertyInfo>? ignoredMembers)
{
return propertyInfo.IsVirtual() &&
ignoredMembers?.TryGetValue(propertyInfo.Name, out JsonPropertyInfo? ignoredMember) == true &&
ignoredMember.IsVirtual &&
propertyInfo.PropertyType == ignoredMember.PropertyType;
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo, NullabilityInfoContext nullabilityCtx)
{
Debug.Assert(typeInfo.Converter.ConstructorInfo != null);
ParameterInfo[] parameters = typeInfo.Converter.ConstructorInfo.GetParameters();
// Count non-out parameters - out parameters don't receive values from JSON.
int nonOutParameterCount = 0;
foreach (ParameterInfo param in parameters)
{
if (!param.IsOut)
{
nonOutParameterCount++;
}
}
JsonParameterInfoValues[] jsonParameters = new JsonParameterInfoValues[nonOutParameterCount];
int jsonParamIndex = 0;
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo reflectionInfo = parameters[i];
// Skip out parameters - they don't receive values from JSON deserialization.
if (reflectionInfo.IsOut)
{
continue;
}
// Trimmed parameter names are reported as null in CoreCLR or "" in Mono.
if (string.IsNullOrEmpty(reflectionInfo.Name))
{
Debug.Assert(typeInfo.Converter.ConstructorInfo.DeclaringType != null);
ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(typeInfo.Converter.ConstructorInfo.DeclaringType);
}
// For byref parameters (in/ref), use the underlying element type.
Type parameterType = reflectionInfo.ParameterType;
if (parameterType.IsByRef)
{
parameterType = parameterType.GetElementType()!;
}
JsonParameterInfoValues jsonInfo = new()
{
Name = reflectionInfo.Name,
ParameterType = parameterType,
Position = jsonParamIndex, // Use the position in the args array, not the constructor parameter index
HasDefaultValue = reflectionInfo.HasDefaultValue,
DefaultValue = reflectionInfo.GetDefaultValue(),
IsNullable = DetermineParameterNullability(reflectionInfo, nullabilityCtx) is not NullabilityState.NotNull,
};
jsonParameters[jsonParamIndex] = jsonInfo;
jsonParamIndex++;
}
typeInfo.PopulateParameterInfoValues(jsonParameters);
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void PopulatePropertyInfo(
JsonPropertyInfo jsonPropertyInfo,
MemberInfo memberInfo,
JsonConverter? customConverter,
JsonIgnoreCondition? ignoreCondition,
NullabilityInfoContext nullabilityCtx,
bool shouldCheckForRequiredKeyword,
bool hasJsonIncludeAttribute,
JsonNamingPolicy? typeNamingPolicy)
{
Debug.Assert(jsonPropertyInfo.AttributeProvider == null);
switch (jsonPropertyInfo.AttributeProvider = memberInfo)
{
case PropertyInfo propertyInfo:
jsonPropertyInfo.MemberName = propertyInfo.Name;
jsonPropertyInfo.IsVirtual = propertyInfo.IsVirtual();
jsonPropertyInfo.MemberType = MemberTypes.Property;
break;
case FieldInfo fieldInfo:
jsonPropertyInfo.MemberName = fieldInfo.Name;
jsonPropertyInfo.MemberType = MemberTypes.Field;
break;
default:
Debug.Fail("Only FieldInfo and PropertyInfo members are supported.");
break;
}
jsonPropertyInfo.CustomConverter = customConverter;
DeterminePropertyPolicies(jsonPropertyInfo, memberInfo);
DeterminePropertyName(jsonPropertyInfo, memberInfo, typeNamingPolicy);
DeterminePropertyIsRequired(jsonPropertyInfo, memberInfo, shouldCheckForRequiredKeyword);
DeterminePropertyNullability(jsonPropertyInfo, memberInfo, nullabilityCtx);
if (ignoreCondition != JsonIgnoreCondition.Always)
{
jsonPropertyInfo.DetermineReflectionPropertyAccessors(memberInfo, useNonPublicAccessors: hasJsonIncludeAttribute);
}
jsonPropertyInfo.IgnoreCondition = ignoreCondition;
jsonPropertyInfo.IsExtensionData = memberInfo.GetCustomAttribute<JsonExtensionDataAttribute>(inherit: false) != null;
}
private static void DeterminePropertyPolicies(JsonPropertyInfo propertyInfo, MemberInfo memberInfo)
{
JsonPropertyOrderAttribute? orderAttr = memberInfo.GetCustomAttribute<JsonPropertyOrderAttribute>(inherit: false);
propertyInfo.Order = orderAttr?.Order ?? 0;
JsonNumberHandlingAttribute? numberHandlingAttr = memberInfo.GetCustomAttribute<JsonNumberHandlingAttribute>(inherit: false);
propertyInfo.NumberHandling = numberHandlingAttr?.Handling;
JsonObjectCreationHandlingAttribute? objectCreationHandlingAttr = memberInfo.GetCustomAttribute<JsonObjectCreationHandlingAttribute>(inherit: false);
propertyInfo.ObjectCreationHandling = objectCreationHandlingAttr?.Handling;
}
private static void DeterminePropertyName(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, JsonNamingPolicy? typeNamingPolicy)
{
JsonPropertyNameAttribute? nameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(inherit: false);
string? name;
if (nameAttribute != null)
{
name = nameAttribute.Name;
}
else
{
JsonNamingPolicy? effectivePolicy = memberInfo.GetCustomAttribute<JsonNamingPolicyAttribute>(inherit: false)?.NamingPolicy
?? typeNamingPolicy
?? propertyInfo.Options.PropertyNamingPolicy;
name = effectivePolicy is not null
? effectivePolicy.ConvertName(memberInfo.Name)
: memberInfo.Name;
}
if (name == null)
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(propertyInfo);
}
propertyInfo.Name = name;
}
private static void DeterminePropertyIsRequired(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, bool shouldCheckForRequiredKeyword)
{
propertyInfo.IsRequired =
memberInfo.GetCustomAttribute<JsonRequiredAttribute>(inherit: false) != null
|| (shouldCheckForRequiredKeyword && memberInfo.HasRequiredMemberAttribute());
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
internal static void DeterminePropertyAccessors<T>(JsonPropertyInfo<T> jsonPropertyInfo, MemberInfo memberInfo, bool useNonPublicAccessors)
{
Debug.Assert(memberInfo is FieldInfo or PropertyInfo);
switch (memberInfo)
{
case PropertyInfo propertyInfo:
MethodInfo? getMethod = propertyInfo.GetMethod;
if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors))
{
jsonPropertyInfo.Get = MemberAccessor.CreatePropertyGetter<T>(propertyInfo);
}
MethodInfo? setMethod = propertyInfo.SetMethod;
if (setMethod != null && (setMethod.IsPublic || useNonPublicAccessors))
{
jsonPropertyInfo.Set = MemberAccessor.CreatePropertySetter<T>(propertyInfo);
}
break;
case FieldInfo fieldInfo:
Debug.Assert(fieldInfo.IsPublic || useNonPublicAccessors);
jsonPropertyInfo.Get = MemberAccessor.CreateFieldGetter<T>(fieldInfo);
if (!fieldInfo.IsInitOnly)
{
jsonPropertyInfo.Set = MemberAccessor.CreateFieldSetter<T>(fieldInfo);
}
break;
default:
Debug.Fail($"Invalid MemberInfo type: {memberInfo.MemberType}");
break;
}
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static Func<object>? DetermineCreateObjectDelegate(Type type, JsonConverter converter)
{
ConstructorInfo? defaultCtor = null;
if (converter.ConstructorInfo != null && !converter.ConstructorIsParameterized)
{
// A parameterless constructor has been resolved by the converter
// (e.g. it might be a non-public ctor with JsonConstructorAttribute).
defaultCtor = converter.ConstructorInfo;
}
// Fall back to resolving any public constructors on the type.
defaultCtor ??= type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null);
return MemberAccessor.CreateParameterlessConstructor(type, defaultCtor);
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void DeterminePropertyNullability(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, NullabilityInfoContext nullabilityCtx)
{
if (!propertyInfo.PropertyTypeCanBeNull)
{
return;
}
NullabilityInfo nullabilityInfo;
if (propertyInfo.MemberType is MemberTypes.Property)
{
nullabilityInfo = nullabilityCtx.Create((PropertyInfo)memberInfo);
}
else
{
Debug.Assert(propertyInfo.MemberType is MemberTypes.Field);
nullabilityInfo = nullabilityCtx.Create((FieldInfo)memberInfo);
}
propertyInfo.IsGetNullable = nullabilityInfo.ReadState is not NullabilityState.NotNull;
propertyInfo.IsSetNullable = nullabilityInfo.WriteState is not NullabilityState.NotNull;
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static NullabilityState DetermineParameterNullability(ParameterInfo parameterInfo, NullabilityInfoContext nullabilityCtx)
{
if (!parameterInfo.ParameterType.IsNullableType())
{
return NullabilityState.NotNull;
}
#if NET8_0
// Workaround for https://github.com/dotnet/runtime/issues/92487
// The fix has been incorporated into .NET 9 (and the polyfilled implementations in netfx).
// Should be removed once .NET 8 support is dropped.
if (parameterInfo.GetGenericParameterDefinition() is { ParameterType: { IsGenericParameter: true } typeParam })
{
// Step 1. Look for nullable annotations on the type parameter.
if (GetNullableFlags(typeParam) is byte[] flags)
{
return TranslateByte(flags[0]);
}
// Step 2. Look for nullable annotations on the generic method declaration.
if (typeParam.DeclaringMethod != null && GetNullableContextFlag(typeParam.DeclaringMethod) is byte flag)
{
return TranslateByte(flag);
}
// Step 3. Look for nullable annotations on the generic type declaration.
if (GetNullableContextFlag(typeParam.DeclaringType!) is byte flag2)
{
return TranslateByte(flag2);
}
// Default to nullable.
return NullabilityState.Nullable;
static byte[]? GetNullableFlags(MemberInfo member)
{
foreach (CustomAttributeData attr in member.GetCustomAttributesData())
{
Type attrType = attr.AttributeType;
if (attrType.Name == "NullableAttribute" && attrType.Namespace == "System.Runtime.CompilerServices")
{
foreach (CustomAttributeTypedArgument ctorArg in attr.ConstructorArguments)
{
switch (ctorArg.Value)
{
case byte flag:
return [flag];
case byte[] flags:
return flags;
}
}
}
}
return null;
}
static byte? GetNullableContextFlag(MemberInfo member)
{
foreach (CustomAttributeData attr in member.GetCustomAttributesData())
{
Type attrType = attr.AttributeType;
if (attrType.Name == "NullableContextAttribute" && attrType.Namespace == "System.Runtime.CompilerServices")
{
foreach (CustomAttributeTypedArgument ctorArg in attr.ConstructorArguments)
{
if (ctorArg.Value is byte flag)
{
return flag;
}
}
}
}
return null;
}
static NullabilityState TranslateByte(byte b) =>
b switch
{
1 => NullabilityState.NotNull,
2 => NullabilityState.Nullable,
_ => NullabilityState.Unknown
};
}
#endif
NullabilityInfo nullability = nullabilityCtx.Create(parameterInfo);
return nullability.WriteState;
}
}
}
|