| | | 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 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using System.Reflection; |
| | | 8 | | using System.Runtime.CompilerServices; |
| | | 9 | | using System.Text.Json.Reflection; |
| | | 10 | | |
| | | 11 | | namespace System.Text.Json.Serialization.Metadata |
| | | 12 | | { |
| | | 13 | | public partial class DefaultJsonTypeInfoResolver |
| | | 14 | | { |
| | | 15 | | internal static MemberAccessor MemberAccessor |
| | | 16 | | { |
| | | 17 | | [RequiresUnreferencedCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 18 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 19 | | get |
| | 0 | 20 | | { |
| | 0 | 21 | | return global::System.Text.Json.Serialization.Metadata.MemberAccessor.Instance; |
| | 0 | 22 | | } |
| | | 23 | | } |
| | | 24 | | |
| | | 25 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 26 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 27 | | private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options |
| | 0 | 28 | | { |
| | 0 | 29 | | JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, converter, options); |
| | | 30 | | |
| | 0 | 31 | | if (GetNumberHandlingForType(typeInfo.Type) is { } numberHandling) |
| | 0 | 32 | | { |
| | 0 | 33 | | typeInfo.NumberHandling = numberHandling; |
| | 0 | 34 | | } |
| | | 35 | | |
| | 0 | 36 | | if (GetObjectCreationHandlingForType(typeInfo.Type) is { } creationHandling) |
| | 0 | 37 | | { |
| | 0 | 38 | | typeInfo.PreferredPropertyObjectCreationHandling = creationHandling; |
| | 0 | 39 | | } |
| | | 40 | | |
| | 0 | 41 | | if (GetUnmappedMemberHandling(typeInfo.Type) is { } unmappedMemberHandling |
| | 0 | 42 | | && typeInfo.Kind is JsonTypeInfoKind.Object) |
| | 0 | 43 | | { |
| | 0 | 44 | | typeInfo.UnmappedMemberHandling = unmappedMemberHandling; |
| | 0 | 45 | | } |
| | | 46 | | |
| | 0 | 47 | | PopulatePolymorphismMetadata(typeInfo); |
| | | 48 | | |
| | 0 | 49 | | typeInfo.MapInterfaceTypesToCallbacks(); |
| | | 50 | | |
| | 0 | 51 | | Func<object>? createObject = DetermineCreateObjectDelegate(type, converter); |
| | 0 | 52 | | typeInfo.SetCreateObjectIfCompatible(createObject); |
| | 0 | 53 | | typeInfo.CreateObjectForExtensionDataProperty = createObject; |
| | | 54 | | |
| | 0 | 55 | | if (typeInfo is { Kind: JsonTypeInfoKind.Object, IsNullable: false }) |
| | 0 | 56 | | { |
| | 0 | 57 | | NullabilityInfoContext nullabilityCtx = new(); |
| | | 58 | | |
| | 0 | 59 | | if (converter.ConstructorIsParameterized) |
| | 0 | 60 | | { |
| | | 61 | | // NB parameter metadata must be populated *before* property metadata |
| | | 62 | | // so that properties can be linked to their associated parameters. |
| | 0 | 63 | | PopulateParameterInfoValues(typeInfo, nullabilityCtx); |
| | 0 | 64 | | } |
| | | 65 | | |
| | 0 | 66 | | PopulateProperties(typeInfo, nullabilityCtx); |
| | | 67 | | |
| | 0 | 68 | | typeInfo.ConstructorAttributeProvider = typeInfo.Converter.ConstructorInfo; |
| | 0 | 69 | | } |
| | | 70 | | |
| | 0 | 71 | | if (typeInfo.Kind is JsonTypeInfoKind.Union) |
| | 0 | 72 | | { |
| | 0 | 73 | | PopulateUnionMetadata(typeInfo); |
| | 0 | 74 | | } |
| | | 75 | | |
| | | 76 | | // Plug in any converter configuration -- should be run last. |
| | 0 | 77 | | converter.ConfigureJsonTypeInfo(typeInfo, options); |
| | 0 | 78 | | converter.ConfigureJsonTypeInfoUsingReflection(typeInfo, options); |
| | 0 | 79 | | return typeInfo; |
| | 0 | 80 | | } |
| | | 81 | | |
| | | 82 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 83 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 84 | | internal static void PopulatePolymorphismMetadata(JsonTypeInfo typeInfo) |
| | 0 | 85 | | { |
| | 0 | 86 | | Debug.Assert(!typeInfo.IsReadOnly); |
| | | 87 | | |
| | 0 | 88 | | JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(typeInfo.Type, ou |
| | | 89 | | |
| | | 90 | | // 'closed' is a C# language feature that can be used on any target framework, including .NET Framework. |
| | | 91 | | // When targeting a runtime that predates System.Runtime.CompilerServices.IsClosedTypeAttribute, the C# |
| | | 92 | | // compiler polyfills the attribute directly into the consuming assembly. We therefore detect it by full |
| | | 93 | | // name (as we do for other compiler-emitted attributes such as RequiredMemberAttribute) instead of via a |
| | | 94 | | // compile-time type reference, so that inference works on every target framework and regardless of whether |
| | | 95 | | // the attribute is provided by the runtime or polyfilled by the compiler. |
| | | 96 | | // |
| | | 97 | | // Consistent with other type-scoped settings such as JsonNumberHandlingAttribute, a value specified on the |
| | | 98 | | // declaration overrides the global JsonSerializerOptions setting; the global value applies when unset. |
| | 0 | 99 | | bool? inferClosedTypePolymorphismOverride = polymorphicAttribute?.InferClosedTypePolymorphismOrNull; |
| | | 100 | | |
| | 0 | 101 | | bool inferClosedTypePolymorphism = inferClosedTypePolymorphismOverride ?? typeInfo.Options.InferClosedTypePo |
| | | 102 | | |
| | | 103 | | // A single attribute walk answers both questions. It is skipped entirely unless inference could |
| | | 104 | | // apply, so ordinary type resolution does not pay for it: an explicit opt-in always needs the |
| | | 105 | | // answer, while a global opt-in only matters when the declaration registers no derived types. |
| | 0 | 106 | | bool isClosedType = false; |
| | 0 | 107 | | Type[]? closedDerivedTypes = null; |
| | | 108 | | |
| | 0 | 109 | | if (inferClosedTypePolymorphismOverride is true || |
| | 0 | 110 | | (inferClosedTypePolymorphism && options is null or { DerivedTypes.Count: 0 })) |
| | 0 | 111 | | { |
| | 0 | 112 | | isClosedType = IsClosedType(typeInfo.Type, out closedDerivedTypes); |
| | 0 | 113 | | } |
| | | 114 | | |
| | 0 | 115 | | if (inferClosedTypePolymorphismOverride is true && !isClosedType) |
| | 0 | 116 | | { |
| | | 117 | | // Enabling inference on a type that is not closed can never infer derived types, so it is |
| | | 118 | | // always a mistake -- including when the declaration carries explicit JsonDerivedTypeAttribute |
| | | 119 | | // registrations, which would otherwise mask the error behind a working hierarchy. This matches |
| | | 120 | | // the SYSLIB1243 error reported by the source generator. |
| | 0 | 121 | | ThrowHelper.ThrowInvalidOperationException_InferClosedTypePolymorphismOnNonClosedType(typeInfo.Type); |
| | | 122 | | } |
| | | 123 | | |
| | 0 | 124 | | if (inferClosedTypePolymorphism && |
| | 0 | 125 | | options is null or { DerivedTypes.Count: 0 } && |
| | 0 | 126 | | closedDerivedTypes is { Length: > 0 } declaredDerivedTypes) |
| | 0 | 127 | | { |
| | 0 | 128 | | options ??= new(); |
| | | 129 | | |
| | 0 | 130 | | List<Type> inferredDerivedTypes = new(declaredDerivedTypes.Length); |
| | 0 | 131 | | GetClosedDerivedTypes(declaredDerivedTypes, inferredDerivedTypes); |
| | | 132 | | |
| | 0 | 133 | | foreach (Type derivedType in inferredDerivedTypes) |
| | 0 | 134 | | { |
| | | 135 | | // An inferred derived type must be at least as visible as the base type it is |
| | | 136 | | // being registered under; otherwise there are call sites that can see the base but |
| | | 137 | | // not the derived type, and source-gen could not emit a reference to it. |
| | 0 | 138 | | if (!derivedType.IsAtLeastAsVisibleAs(typeInfo.Type)) |
| | 0 | 139 | | { |
| | 0 | 140 | | ThrowHelper.ThrowInvalidOperationException_InferredDerivedTypeIsNotAccessible(typeInfo.Type, der |
| | | 141 | | } |
| | | 142 | | |
| | 0 | 143 | | options.DerivedTypes.Add(new JsonDerivedType(derivedType, GetInferredTypeDiscriminator(derivedType)) |
| | 0 | 144 | | } |
| | 0 | 145 | | } |
| | 0 | 146 | | else if (inferClosedTypePolymorphismOverride is false && |
| | 0 | 147 | | options is { DerivedTypes.Count: 0 } && |
| | 0 | 148 | | polymorphicAttribute is not null && |
| | 0 | 149 | | !HasNonDefaultPolymorphismSettings(polymorphicAttribute)) |
| | 0 | 150 | | { |
| | | 151 | | // The declaration explicitly opted out of inference without specifying any other polymorphism |
| | | 152 | | // metadata, so JsonPolymorphicAttribute is being used to exclude the type from a global opt-in |
| | | 153 | | // rather than to declare a hierarchy. Leave the type non-polymorphic instead of failing |
| | | 154 | | // configuration on an empty registration list. |
| | | 155 | | // |
| | | 156 | | // Any other empty registration list -- including a closed type that declares no derived types |
| | | 157 | | // -- falls through to the pre-existing 'should specify at least one derived type' failure that |
| | | 158 | | // JsonPolymorphicAttribute has always produced when no derived types are registered. |
| | 0 | 159 | | options = null; |
| | 0 | 160 | | } |
| | | 161 | | |
| | 0 | 162 | | if (options is not null) |
| | 0 | 163 | | { |
| | 0 | 164 | | ResolveOpenGenericDerivedTypes(typeInfo.Type, options.DerivedTypes); |
| | 0 | 165 | | typeInfo.SetPolymorphismOptions(options); |
| | 0 | 166 | | } |
| | | 167 | | |
| | 0 | 168 | | if (typeInfo.Kind is not JsonTypeInfoKind.Union) |
| | 0 | 169 | | { |
| | 0 | 170 | | if (polymorphicAttribute?.TypeClassifier is Type classifierFactoryType) |
| | 0 | 171 | | { |
| | 0 | 172 | | if (!typeof(JsonTypeClassifierFactory).IsAssignableFrom(classifierFactoryType)) |
| | 0 | 173 | | { |
| | 0 | 174 | | ThrowHelper.ThrowInvalidOperationException_TypeClassifierMustDeriveFromJsonTypeClassifierFactory |
| | | 175 | | } |
| | | 176 | | |
| | 0 | 177 | | typeInfo.TypeClassifierFactory = (JsonTypeClassifierFactory)Activator.CreateInstance(classifierFacto |
| | 0 | 178 | | } |
| | | 179 | | |
| | 0 | 180 | | if (typeInfo.PolymorphismOptions is not null && |
| | 0 | 181 | | (typeInfo.TypeClassifierFactory is not null || typeInfo.Options.TypeClassifiers.Count > 0)) |
| | 0 | 182 | | { |
| | 0 | 183 | | typeInfo.TypeClassifierResolutionPending = true; |
| | 0 | 184 | | } |
| | 0 | 185 | | } |
| | | 186 | | |
| | | 187 | | static string GetInferredTypeDiscriminator(Type type) |
| | 0 | 188 | | { |
| | 0 | 189 | | string name = type.Name; |
| | 0 | 190 | | int genericAritySeparatorIndex = name.IndexOf('`'); |
| | 0 | 191 | | return genericAritySeparatorIndex < 0 ? name : name.Substring(0, genericAritySeparatorIndex); |
| | 0 | 192 | | } |
| | | 193 | | |
| | | 194 | | static bool HasNonDefaultPolymorphismSettings(JsonPolymorphicAttribute attribute) => |
| | 0 | 195 | | attribute.IgnoreUnrecognizedTypeDiscriminators || |
| | 0 | 196 | | attribute.TypeClassifier is not null || |
| | 0 | 197 | | attribute.TypeDiscriminatorPropertyName is not null || |
| | 0 | 198 | | attribute.UnknownDerivedTypeHandling != default; |
| | 0 | 199 | | } |
| | | 200 | | |
| | | 201 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 202 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 203 | | private static void ResolveOpenGenericDerivedTypes(Type baseType, IList<JsonDerivedType> derivedTypes) |
| | 0 | 204 | | { |
| | 0 | 205 | | Type? baseTypeDefinition = null; |
| | 0 | 206 | | Type[]? baseTypeArgs = null; |
| | | 207 | | |
| | 0 | 208 | | for (int i = 0; i < derivedTypes.Count; i++) |
| | 0 | 209 | | { |
| | 0 | 210 | | JsonDerivedType entry = derivedTypes[i]; |
| | | 211 | | |
| | 0 | 212 | | if (entry.DerivedType is null || !entry.DerivedType.IsGenericTypeDefinition) |
| | 0 | 213 | | { |
| | | 214 | | // entry.DerivedType is annotated non-nullable, but the public |
| | | 215 | | // JsonDerivedType constructors do not validate the argument, so a |
| | | 216 | | // [JsonDerivedType(derivedType: null)] attribute (or an explicit |
| | | 217 | | // null) yields an entry with DerivedType == null. The downstream |
| | | 218 | | // validation in PolymorphicTypeResolver will surface a friendly |
| | | 219 | | // diagnostic; skip silently here so the explicit error wins. |
| | 0 | 220 | | continue; |
| | | 221 | | } |
| | | 222 | | |
| | 0 | 223 | | if (!baseType.IsGenericType) |
| | 0 | 224 | | { |
| | 0 | 225 | | ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeCouldNotBeResolved( |
| | 0 | 226 | | baseType, entry.DerivedType, SR.Polymorphism_OpenGeneric_Reason_NotAssignable); |
| | | 227 | | } |
| | | 228 | | |
| | 0 | 229 | | baseTypeDefinition ??= baseType.GetGenericTypeDefinition(); |
| | 0 | 230 | | baseTypeArgs ??= baseType.GetGenericArguments(); |
| | | 231 | | |
| | 0 | 232 | | if (!TryResolveOpenGenericDerivedType( |
| | 0 | 233 | | entry.DerivedType, baseTypeDefinition, baseTypeArgs, |
| | 0 | 234 | | out Type? resolvedType, out string? failureReason)) |
| | 0 | 235 | | { |
| | 0 | 236 | | ThrowHelper.ThrowInvalidOperationException_OpenGenericDerivedTypeCouldNotBeResolved( |
| | 0 | 237 | | baseType, entry.DerivedType, failureReason!); |
| | | 238 | | } |
| | | 239 | | |
| | 0 | 240 | | derivedTypes[i] = new JsonDerivedType(resolvedType!, entry.TypeDiscriminator); |
| | 0 | 241 | | } |
| | 0 | 242 | | } |
| | | 243 | | |
| | | 244 | | /// <summary> |
| | | 245 | | /// Determines whether <paramref name="type"/> is declared <see langword="closed"/>, reporting the |
| | | 246 | | /// derived types the compiler recorded for it in <paramref name="derivedTypes"/>. |
| | | 247 | | /// |
| | | 248 | | /// The two results are independent: a closed type that declares no derived types is still a closed |
| | | 249 | | /// type, so <paramref name="derivedTypes"/> is <see langword="null"/> both for a type that is not |
| | | 250 | | /// closed and for a closed type with an empty hierarchy. Inference only consults |
| | | 251 | | /// <paramref name="derivedTypes"/> and is therefore inert on any type that is not closed, but |
| | | 252 | | /// diagnosing an explicit <see cref="JsonPolymorphicAttribute.InferClosedTypePolymorphism"/> opt-in |
| | | 253 | | /// has to tell those two cases apart -- the first is an error, the second a valid declaration. |
| | | 254 | | /// |
| | | 255 | | /// The marker attribute is matched by full name because the C# compiler polyfills |
| | | 256 | | /// <c>System.Runtime.CompilerServices.IsClosedTypeAttribute</c> into assemblies that target runtimes |
| | | 257 | | /// without the type, making the polyfilled copy distinct from any runtime-provided one. |
| | | 258 | | /// </summary> |
| | | 259 | | private static bool IsClosedType(Type type, out Type[]? derivedTypes) |
| | 0 | 260 | | { |
| | 0 | 261 | | derivedTypes = null; |
| | | 262 | | |
| | 0 | 263 | | foreach (CustomAttributeData attributeData in type.GetCustomAttributesData()) |
| | 0 | 264 | | { |
| | 0 | 265 | | Type attributeType = attributeData.AttributeType; |
| | 0 | 266 | | if (attributeType.Name != "IsClosedTypeAttribute" || |
| | 0 | 267 | | attributeType.FullName != "System.Runtime.CompilerServices.IsClosedTypeAttribute") |
| | 0 | 268 | | { |
| | 0 | 269 | | continue; |
| | | 270 | | } |
| | | 271 | | |
| | 0 | 272 | | derivedTypes = GetDeclaredDerivedTypes(attributeData); |
| | 0 | 273 | | return true; |
| | | 274 | | } |
| | | 275 | | |
| | 0 | 276 | | return false; |
| | 0 | 277 | | } |
| | | 278 | | |
| | | 279 | | private static void GetClosedDerivedTypes(Type[] declaredDerivedTypes, List<Type> derivedTypes) |
| | 0 | 280 | | { |
| | 0 | 281 | | foreach (Type derivedType in declaredDerivedTypes) |
| | 0 | 282 | | { |
| | 0 | 283 | | Type derivedTypeDefinition = derivedType.IsGenericType |
| | 0 | 284 | | ? derivedType.GetGenericTypeDefinition() |
| | 0 | 285 | | : derivedType; |
| | | 286 | | |
| | 0 | 287 | | if (IsClosedType(derivedTypeDefinition, out Type[]? nestedDerivedTypes)) |
| | 0 | 288 | | { |
| | 0 | 289 | | if (nestedDerivedTypes is not null) |
| | 0 | 290 | | { |
| | 0 | 291 | | GetClosedDerivedTypes(nestedDerivedTypes, derivedTypes); |
| | 0 | 292 | | } |
| | 0 | 293 | | } |
| | | 294 | | else |
| | 0 | 295 | | { |
| | 0 | 296 | | derivedTypes.Add(derivedType); |
| | 0 | 297 | | } |
| | 0 | 298 | | } |
| | 0 | 299 | | } |
| | | 300 | | |
| | | 301 | | /// <summary> |
| | | 302 | | /// Reads the derived-type list from the compiler-emitted closed-type marker attribute. Returns |
| | | 303 | | /// <see langword="null"/> when the hierarchy is empty or the attribute cannot be interpreted. |
| | | 304 | | /// </summary> |
| | | 305 | | private static Type[]? GetDeclaredDerivedTypes(CustomAttributeData attributeData) |
| | 0 | 306 | | { |
| | 0 | 307 | | foreach (CustomAttributeNamedArgument namedArgument in attributeData.NamedArguments) |
| | 0 | 308 | | { |
| | 0 | 309 | | if (namedArgument.MemberName != "DerivedTypes") |
| | 0 | 310 | | { |
| | 0 | 311 | | continue; |
| | | 312 | | } |
| | | 313 | | |
| | 0 | 314 | | object? value = namedArgument.TypedValue.Value; |
| | | 315 | | |
| | | 316 | | // Mono materializes array-valued named arguments directly, while CoreCLR wraps |
| | | 317 | | // each element in a CustomAttributeTypedArgument. |
| | 0 | 318 | | if (value is Type[] materializedDerivedTypes) |
| | 0 | 319 | | { |
| | 0 | 320 | | foreach (Type? derivedType in materializedDerivedTypes) |
| | 0 | 321 | | { |
| | 0 | 322 | | if (derivedType is null) |
| | 0 | 323 | | { |
| | 0 | 324 | | return null; |
| | | 325 | | } |
| | 0 | 326 | | } |
| | | 327 | | |
| | 0 | 328 | | return materializedDerivedTypes; |
| | | 329 | | } |
| | | 330 | | |
| | 0 | 331 | | if (value is not IList<CustomAttributeTypedArgument> derivedTypeArguments) |
| | 0 | 332 | | { |
| | 0 | 333 | | return null; |
| | | 334 | | } |
| | | 335 | | |
| | 0 | 336 | | Type[] derivedTypes = new Type[derivedTypeArguments.Count]; |
| | 0 | 337 | | for (int i = 0; i < derivedTypes.Length; i++) |
| | 0 | 338 | | { |
| | 0 | 339 | | if (derivedTypeArguments[i].Value is not Type derivedType) |
| | 0 | 340 | | { |
| | 0 | 341 | | return null; |
| | | 342 | | } |
| | | 343 | | |
| | 0 | 344 | | derivedTypes[i] = derivedType; |
| | 0 | 345 | | } |
| | | 346 | | |
| | 0 | 347 | | return derivedTypes; |
| | | 348 | | } |
| | | 349 | | |
| | | 350 | | // The closed-type marker is present but carries no derived types. |
| | 0 | 351 | | return null; |
| | 0 | 352 | | } |
| | | 353 | | |
| | | 354 | | /// <summary> |
| | | 355 | | /// Reflection-side resolver: closes <paramref name="openDerivedType"/> against the |
| | | 356 | | /// constructed base type identified by (<paramref name="baseTypeDefinition"/>, |
| | | 357 | | /// <paramref name="baseTypeArgs"/>) via structural unification. |
| | | 358 | | /// |
| | | 359 | | /// IMPORTANT: This implementation MIRRORS the source-gen resolver |
| | | 360 | | /// <c>JsonSourceGenerator.Parser.TryResolveOpenGenericDerivedType</c> in |
| | | 361 | | /// gen/JsonSourceGenerator.Parser.cs. Both implementations -- the structural |
| | | 362 | | /// unbound pre-check, the per-ancestor unification, and the ambiguity |
| | | 363 | | /// detection -- must be kept in lockstep so that reflection and source-gen |
| | | 364 | | /// produce the same closed type for the same registration. Any algorithmic |
| | | 365 | | /// change here must be applied in the source-gen mirror as well. |
| | | 366 | | /// |
| | | 367 | | /// Known intentional asymmetry with the source-gen mirror: source-gen rejects a |
| | | 368 | | /// managed value type (e.g. a struct containing reference fields) supplied for a |
| | | 369 | | /// <c>where T : unmanaged</c> constraint because emitting such a closed type would |
| | | 370 | | /// produce a C# compile error (CS8377). The reflection resolver, by contrast, |
| | | 371 | | /// delegates constraint validation to <see cref="Type.MakeGenericType"/>, which only |
| | | 372 | | /// enforces the underlying value-type part of the constraint at runtime (the |
| | | 373 | | /// <c>unmanaged</c> modreq is not surfaced through standard reflection metadata). |
| | | 374 | | /// As a result, reflection accepts managed structs for <c>unmanaged</c>-constrained |
| | | 375 | | /// derived types where source-gen rejects them. This divergence cannot be bridged |
| | | 376 | | /// without emitting invalid C# code on the source-gen side. |
| | | 377 | | /// </summary> |
| | | 378 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 379 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 380 | | private static bool TryResolveOpenGenericDerivedType( |
| | | 381 | | Type openDerivedType, |
| | | 382 | | Type baseTypeDefinition, |
| | | 383 | | Type[] baseTypeArgs, |
| | | 384 | | out Type? closedDerivedType, |
| | | 385 | | out string? failureReason) |
| | 0 | 386 | | { |
| | 0 | 387 | | closedDerivedType = null; |
| | 0 | 388 | | failureReason = null; |
| | | 389 | | |
| | | 390 | | // Find every ancestor of the open derived type whose generic type definition matches |
| | | 391 | | // the base type definition. For classes there is at most one such ancestor, but for |
| | | 392 | | // interfaces a derived type can implement the same interface definition multiple times |
| | | 393 | | // with different type arguments (e.g. Derived<T> : IBase<T>, IBase<List<T>>). |
| | 0 | 394 | | List<Type> matchingBases = new(); |
| | 0 | 395 | | foreach (Type match in openDerivedType.GetMatchingGenericBaseTypes(baseTypeDefinition)) |
| | 0 | 396 | | { |
| | 0 | 397 | | matchingBases.Add(match); |
| | 0 | 398 | | } |
| | | 399 | | |
| | 0 | 400 | | if (matchingBases.Count == 0) |
| | 0 | 401 | | { |
| | 0 | 402 | | failureReason = SR.Polymorphism_OpenGeneric_Reason_NotAssignable; |
| | 0 | 403 | | return false; |
| | | 404 | | } |
| | | 405 | | |
| | | 406 | | // The full set of generic parameters we must bind includes the parameters of the |
| | | 407 | | // derived type itself plus any parameters declared by enclosing generic types |
| | | 408 | | // (e.g. Outer<T>.Derived needs T bound from the outer class). |
| | | 409 | | // Type.GetGenericArguments() on an open generic type returns this complete set. |
| | 0 | 410 | | Type[] requiredParams = openDerivedType.GetGenericArguments(); |
| | | 411 | | |
| | | 412 | | // Structural unbound pre-check: every required parameter must appear at least once |
| | | 413 | | // somewhere in some matching ancestor's type arguments. If a parameter never appears |
| | | 414 | | // at all, no closed base could ever bind it -- the derived definition is malformed |
| | | 415 | | // regardless of which closed base it is registered against. |
| | 0 | 416 | | HashSet<Type> referencedParams = new(); |
| | 0 | 417 | | foreach (Type mb in matchingBases) |
| | 0 | 418 | | { |
| | 0 | 419 | | foreach (Type arg in mb.GetGenericArguments()) |
| | 0 | 420 | | { |
| | 0 | 421 | | CollectReferencedParameters(arg, referencedParams); |
| | 0 | 422 | | } |
| | 0 | 423 | | } |
| | 0 | 424 | | foreach (Type required in requiredParams) |
| | 0 | 425 | | { |
| | 0 | 426 | | if (!referencedParams.Contains(required)) |
| | 0 | 427 | | { |
| | 0 | 428 | | failureReason = SR.Format(SR.Polymorphism_OpenGeneric_Reason_UnboundParameter, required.Name); |
| | 0 | 429 | | return false; |
| | | 430 | | } |
| | 0 | 431 | | } |
| | | 432 | | |
| | 0 | 433 | | Type[]? successfulArgs = null; |
| | 0 | 434 | | int successCount = 0; |
| | | 435 | | |
| | 0 | 436 | | foreach (Type matchingBase in matchingBases) |
| | 0 | 437 | | { |
| | 0 | 438 | | Type[] matchingBaseArgs = matchingBase.GetGenericArguments(); |
| | 0 | 439 | | Debug.Assert(matchingBaseArgs.Length == baseTypeArgs.Length, |
| | 0 | 440 | | "matchingBase and baseTypeArgs share the same generic type definition, so arity must match."); |
| | | 441 | | |
| | 0 | 442 | | var substitution = new Dictionary<Type, Type>(requiredParams.Length); |
| | 0 | 443 | | bool unified = true; |
| | 0 | 444 | | for (int i = 0; i < matchingBaseArgs.Length; i++) |
| | 0 | 445 | | { |
| | 0 | 446 | | if (!matchingBaseArgs[i].TryUnifyWith(baseTypeArgs[i], substitution)) |
| | 0 | 447 | | { |
| | 0 | 448 | | unified = false; |
| | 0 | 449 | | break; |
| | | 450 | | } |
| | 0 | 451 | | } |
| | | 452 | | |
| | 0 | 453 | | if (!unified) |
| | 0 | 454 | | { |
| | 0 | 455 | | continue; |
| | | 456 | | } |
| | | 457 | | |
| | | 458 | | // Unification succeeded for every position. Every required parameter of the |
| | | 459 | | // derived type definition must be bound by this ancestor; otherwise the |
| | | 460 | | // resulting closed type would have unbound type arguments (an unspeakable type). |
| | | 461 | | // A sibling ancestor may still bind this parameter, so failure here is not fatal. |
| | 0 | 462 | | Type[] closedArgs = new Type[requiredParams.Length]; |
| | 0 | 463 | | bool allBound = true; |
| | 0 | 464 | | for (int i = 0; i < requiredParams.Length; i++) |
| | 0 | 465 | | { |
| | 0 | 466 | | if (!substitution.TryGetValue(requiredParams[i], out Type? boundArg)) |
| | 0 | 467 | | { |
| | 0 | 468 | | allBound = false; |
| | 0 | 469 | | break; |
| | | 470 | | } |
| | | 471 | | |
| | 0 | 472 | | closedArgs[i] = boundArg; |
| | 0 | 473 | | } |
| | | 474 | | |
| | 0 | 475 | | if (!allBound) |
| | 0 | 476 | | { |
| | 0 | 477 | | continue; |
| | | 478 | | } |
| | | 479 | | |
| | 0 | 480 | | successCount++; |
| | 0 | 481 | | if (successCount == 1) |
| | 0 | 482 | | { |
| | 0 | 483 | | successfulArgs = closedArgs; |
| | 0 | 484 | | } |
| | | 485 | | else |
| | 0 | 486 | | { |
| | 0 | 487 | | failureReason = SR.Polymorphism_OpenGeneric_Reason_AmbiguousMatch; |
| | 0 | 488 | | return false; |
| | | 489 | | } |
| | 0 | 490 | | } |
| | | 491 | | |
| | 0 | 492 | | if (successCount == 0 || successfulArgs is null) |
| | 0 | 493 | | { |
| | 0 | 494 | | failureReason = SR.Polymorphism_OpenGeneric_Reason_UnificationFailed; |
| | 0 | 495 | | return false; |
| | | 496 | | } |
| | | 497 | | |
| | | 498 | | try |
| | 0 | 499 | | { |
| | 0 | 500 | | closedDerivedType = openDerivedType.MakeGenericType(successfulArgs); |
| | 0 | 501 | | return true; |
| | | 502 | | } |
| | 0 | 503 | | catch (Exception ex) when (ex is ArgumentException or TypeLoadException) |
| | 0 | 504 | | { |
| | | 505 | | // Constraint violation or load failure (e.g. unmanaged constraint, which is |
| | | 506 | | // not observable via the standard reflection constraint metadata). We use a |
| | | 507 | | // structured reason rather than ex.Message so that the outer template — which |
| | | 508 | | // appends its own trailing period — never produces a double period. |
| | 0 | 509 | | failureReason = SR.Polymorphism_OpenGeneric_Reason_ConstraintViolation; |
| | 0 | 510 | | return false; |
| | | 511 | | } |
| | 0 | 512 | | } |
| | | 513 | | |
| | | 514 | | private static void CollectReferencedParameters(Type pattern, HashSet<Type> set) |
| | 0 | 515 | | { |
| | 0 | 516 | | if (pattern.IsGenericParameter) |
| | 0 | 517 | | { |
| | 0 | 518 | | set.Add(pattern); |
| | 0 | 519 | | return; |
| | | 520 | | } |
| | | 521 | | |
| | 0 | 522 | | if (pattern.HasElementType) |
| | 0 | 523 | | { |
| | 0 | 524 | | CollectReferencedParameters(pattern.GetElementType()!, set); |
| | 0 | 525 | | return; |
| | | 526 | | } |
| | | 527 | | |
| | 0 | 528 | | if (pattern.IsGenericType) |
| | 0 | 529 | | { |
| | 0 | 530 | | foreach (Type arg in pattern.GetGenericArguments()) |
| | 0 | 531 | | { |
| | 0 | 532 | | CollectReferencedParameters(arg, set); |
| | 0 | 533 | | } |
| | 0 | 534 | | } |
| | 0 | 535 | | } |
| | | 536 | | |
| | | 537 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 538 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 539 | | private static void PopulateProperties(JsonTypeInfo typeInfo, NullabilityInfoContext nullabilityCtx) |
| | 0 | 540 | | { |
| | 0 | 541 | | Debug.Assert(!typeInfo.IsReadOnly); |
| | 0 | 542 | | Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object); |
| | | 543 | | |
| | | 544 | | // SetsRequiredMembersAttribute means that all required members are assigned by constructor and therefore th |
| | 0 | 545 | | bool constructorHasSetsRequiredMembersAttribute = |
| | 0 | 546 | | typeInfo.Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false; |
| | | 547 | | |
| | | 548 | | // Resolve the type-level JsonNamingPolicyAttribute once for the entire type. |
| | 0 | 549 | | JsonNamingPolicy? typeNamingPolicy = typeInfo.Type.GetUniqueCustomAttribute<JsonNamingPolicyAttribute>(inher |
| | | 550 | | |
| | | 551 | | // Resolve type-level [JsonIgnore] once per type, rather than per-member. |
| | 0 | 552 | | JsonIgnoreCondition? typeIgnoreCondition = typeInfo.Type.GetUniqueCustomAttribute<JsonIgnoreAttribute>(inher |
| | 0 | 553 | | if (typeIgnoreCondition == JsonIgnoreCondition.Always) |
| | 0 | 554 | | { |
| | 0 | 555 | | ThrowHelper.ThrowInvalidOperationException(SR.DefaultIgnoreConditionInvalid); |
| | | 556 | | } |
| | | 557 | | |
| | 0 | 558 | | JsonTypeInfo.PropertyHierarchyResolutionState state = new(typeInfo.Options); |
| | | 559 | | |
| | | 560 | | // Walk the type hierarchy starting from the current type up to the base type(s) |
| | 0 | 561 | | foreach (Type currentType in typeInfo.Type.GetSortedTypeHierarchy()) |
| | 0 | 562 | | { |
| | 0 | 563 | | if (currentType == typeof(object) || |
| | 0 | 564 | | currentType == typeof(ValueType)) |
| | 0 | 565 | | { |
| | | 566 | | // Don't process any members for typeof(object) or System.ValueType |
| | 0 | 567 | | break; |
| | | 568 | | } |
| | | 569 | | |
| | 0 | 570 | | AddMembersDeclaredBySuperType( |
| | 0 | 571 | | typeInfo, |
| | 0 | 572 | | currentType, |
| | 0 | 573 | | typeNamingPolicy, |
| | 0 | 574 | | nullabilityCtx, |
| | 0 | 575 | | typeIgnoreCondition, |
| | 0 | 576 | | constructorHasSetsRequiredMembersAttribute, |
| | 0 | 577 | | ref state); |
| | 0 | 578 | | } |
| | | 579 | | |
| | 0 | 580 | | if (state.IsPropertyOrderSpecified) |
| | 0 | 581 | | { |
| | 0 | 582 | | typeInfo.PropertyList.SortProperties(); |
| | 0 | 583 | | } |
| | 0 | 584 | | } |
| | | 585 | | |
| | | 586 | | private const BindingFlags AllInstanceMembers = |
| | | 587 | | BindingFlags.Instance | |
| | | 588 | | BindingFlags.Public | |
| | | 589 | | BindingFlags.NonPublic | |
| | | 590 | | BindingFlags.DeclaredOnly; |
| | | 591 | | |
| | | 592 | | |
| | | 593 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 594 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 595 | | private static void AddMembersDeclaredBySuperType( |
| | | 596 | | JsonTypeInfo typeInfo, |
| | | 597 | | Type currentType, |
| | | 598 | | JsonNamingPolicy? typeNamingPolicy, |
| | | 599 | | NullabilityInfoContext nullabilityCtx, |
| | | 600 | | JsonIgnoreCondition? typeIgnoreCondition, |
| | | 601 | | bool constructorHasSetsRequiredMembersAttribute, |
| | | 602 | | ref JsonTypeInfo.PropertyHierarchyResolutionState state) |
| | 0 | 603 | | { |
| | 0 | 604 | | Debug.Assert(!typeInfo.IsReadOnly); |
| | 0 | 605 | | Debug.Assert(currentType.IsAssignableFrom(typeInfo.Type)); |
| | | 606 | | |
| | | 607 | | // Compiler adds RequiredMemberAttribute to type if any of the members are marked with 'required' keyword. |
| | 0 | 608 | | bool shouldCheckMembersForRequiredMemberAttribute = |
| | 0 | 609 | | !constructorHasSetsRequiredMembersAttribute && currentType.HasRequiredMemberAttribute(); |
| | | 610 | | |
| | 0 | 611 | | foreach (PropertyInfo propertyInfo in currentType.GetProperties(AllInstanceMembers)) |
| | 0 | 612 | | { |
| | | 613 | | // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. |
| | 0 | 614 | | if (propertyInfo.GetIndexParameters().Length > 0 || |
| | 0 | 615 | | PropertyIsOverriddenAndIgnored(propertyInfo, state.IgnoredProperties)) |
| | 0 | 616 | | { |
| | 0 | 617 | | continue; |
| | | 618 | | } |
| | | 619 | | |
| | 0 | 620 | | bool hasJsonIncludeAttribute = propertyInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) is |
| | | 621 | | |
| | | 622 | | // Only include properties that either have a public getter or a public setter or have the JsonIncludeAt |
| | 0 | 623 | | if (propertyInfo.GetMethod?.IsPublic == true || |
| | 0 | 624 | | propertyInfo.SetMethod?.IsPublic == true || |
| | 0 | 625 | | hasJsonIncludeAttribute) |
| | 0 | 626 | | { |
| | 0 | 627 | | AddMember( |
| | 0 | 628 | | typeInfo, |
| | 0 | 629 | | typeToConvert: propertyInfo.PropertyType, |
| | 0 | 630 | | memberInfo: propertyInfo, |
| | 0 | 631 | | typeNamingPolicy, |
| | 0 | 632 | | nullabilityCtx, |
| | 0 | 633 | | typeIgnoreCondition, |
| | 0 | 634 | | shouldCheckMembersForRequiredMemberAttribute, |
| | 0 | 635 | | hasJsonIncludeAttribute, |
| | 0 | 636 | | ref state); |
| | 0 | 637 | | } |
| | 0 | 638 | | } |
| | | 639 | | |
| | 0 | 640 | | foreach (FieldInfo fieldInfo in currentType.GetFields(AllInstanceMembers)) |
| | 0 | 641 | | { |
| | 0 | 642 | | bool hasJsonIncludeAttribute = fieldInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) is not |
| | 0 | 643 | | if (hasJsonIncludeAttribute || (fieldInfo.IsPublic && typeInfo.Options.IncludeFields)) |
| | 0 | 644 | | { |
| | 0 | 645 | | AddMember( |
| | 0 | 646 | | typeInfo, |
| | 0 | 647 | | typeToConvert: fieldInfo.FieldType, |
| | 0 | 648 | | memberInfo: fieldInfo, |
| | 0 | 649 | | typeNamingPolicy, |
| | 0 | 650 | | nullabilityCtx, |
| | 0 | 651 | | typeIgnoreCondition, |
| | 0 | 652 | | shouldCheckMembersForRequiredMemberAttribute, |
| | 0 | 653 | | hasJsonIncludeAttribute, |
| | 0 | 654 | | ref state); |
| | 0 | 655 | | } |
| | 0 | 656 | | } |
| | 0 | 657 | | } |
| | | 658 | | |
| | | 659 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 660 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 661 | | private static void AddMember( |
| | | 662 | | JsonTypeInfo typeInfo, |
| | | 663 | | Type typeToConvert, |
| | | 664 | | MemberInfo memberInfo, |
| | | 665 | | JsonNamingPolicy? typeNamingPolicy, |
| | | 666 | | NullabilityInfoContext nullabilityCtx, |
| | | 667 | | JsonIgnoreCondition? typeIgnoreCondition, |
| | | 668 | | bool shouldCheckForRequiredKeyword, |
| | | 669 | | bool hasJsonIncludeAttribute, |
| | | 670 | | ref JsonTypeInfo.PropertyHierarchyResolutionState state) |
| | 0 | 671 | | { |
| | 0 | 672 | | JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeNamingPolic |
| | 0 | 673 | | if (jsonPropertyInfo is null) |
| | 0 | 674 | | { |
| | | 675 | | // ignored invalid property |
| | 0 | 676 | | return; |
| | | 677 | | } |
| | | 678 | | |
| | 0 | 679 | | Debug.Assert(jsonPropertyInfo.Name is not null); |
| | 0 | 680 | | typeInfo.PropertyList.AddPropertyWithConflictResolution(jsonPropertyInfo, ref state); |
| | 0 | 681 | | } |
| | | 682 | | |
| | | 683 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 684 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 685 | | private static JsonPropertyInfo? CreatePropertyInfo( |
| | | 686 | | JsonTypeInfo typeInfo, |
| | | 687 | | Type typeToConvert, |
| | | 688 | | MemberInfo memberInfo, |
| | | 689 | | JsonNamingPolicy? typeNamingPolicy, |
| | | 690 | | NullabilityInfoContext nullabilityCtx, |
| | | 691 | | JsonIgnoreCondition? typeIgnoreCondition, |
| | | 692 | | JsonSerializerOptions options, |
| | | 693 | | bool shouldCheckForRequiredKeyword, |
| | | 694 | | bool hasJsonIncludeAttribute) |
| | 0 | 695 | | { |
| | 0 | 696 | | JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute<JsonIgnoreAttribute>(inherit: false)?.C |
| | | 697 | | |
| | | 698 | | // Fall back to the type-level [JsonIgnore] if no member-level attribute is specified. |
| | 0 | 699 | | if (ignoreCondition is null && typeIgnoreCondition is not null) |
| | 0 | 700 | | { |
| | | 701 | | // WhenWritingNull is invalid for non-nullable value types; treat as Never in that case |
| | | 702 | | // so that the type-level annotation still overrides the global JSO DefaultIgnoreCondition. |
| | 0 | 703 | | ignoreCondition = typeIgnoreCondition == JsonIgnoreCondition.WhenWritingNull && !typeToConvert.IsNullabl |
| | 0 | 704 | | ? JsonIgnoreCondition.Never |
| | 0 | 705 | | : typeIgnoreCondition; |
| | 0 | 706 | | } |
| | | 707 | | |
| | 0 | 708 | | if (JsonTypeInfo.IsInvalidForSerialization(typeToConvert)) |
| | 0 | 709 | | { |
| | 0 | 710 | | if (ignoreCondition == JsonIgnoreCondition.Always) |
| | 0 | 711 | | return null; |
| | | 712 | | |
| | 0 | 713 | | ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(typeToConvert, memberInfo.Declarin |
| | | 714 | | } |
| | | 715 | | |
| | | 716 | | // Resolve any custom converters on the attribute level. |
| | | 717 | | JsonConverter? customConverter; |
| | | 718 | | try |
| | 0 | 719 | | { |
| | 0 | 720 | | customConverter = GetCustomConverterForMember(typeToConvert, memberInfo, options); |
| | 0 | 721 | | } |
| | 0 | 722 | | catch (InvalidOperationException) when (ignoreCondition == JsonIgnoreCondition.Always) |
| | 0 | 723 | | { |
| | | 724 | | // skip property altogether if attribute is invalid and the property is ignored |
| | 0 | 725 | | return null; |
| | | 726 | | } |
| | | 727 | | |
| | 0 | 728 | | JsonPropertyInfo jsonPropertyInfo = typeInfo.CreatePropertyUsingReflection(typeToConvert, declaringType: mem |
| | 0 | 729 | | PopulatePropertyInfo(jsonPropertyInfo, memberInfo, customConverter, ignoreCondition, nullabilityCtx, shouldC |
| | 0 | 730 | | return jsonPropertyInfo; |
| | 0 | 731 | | } |
| | | 732 | | |
| | | 733 | | private static JsonNumberHandling? GetNumberHandlingForType(Type type) |
| | 0 | 734 | | { |
| | 0 | 735 | | JsonNumberHandlingAttribute? numberHandlingAttribute = type.GetUniqueCustomAttribute<JsonNumberHandlingAttri |
| | 0 | 736 | | return numberHandlingAttribute?.Handling; |
| | 0 | 737 | | } |
| | | 738 | | |
| | | 739 | | private static JsonObjectCreationHandling? GetObjectCreationHandlingForType(Type type) |
| | 0 | 740 | | { |
| | 0 | 741 | | JsonObjectCreationHandlingAttribute? creationHandlingAttribute = type.GetUniqueCustomAttribute<JsonObjectCre |
| | 0 | 742 | | return creationHandlingAttribute?.Handling; |
| | 0 | 743 | | } |
| | | 744 | | |
| | | 745 | | private static JsonUnmappedMemberHandling? GetUnmappedMemberHandling(Type type) |
| | 0 | 746 | | { |
| | 0 | 747 | | JsonUnmappedMemberHandlingAttribute? numberHandlingAttribute = type.GetUniqueCustomAttribute<JsonUnmappedMem |
| | 0 | 748 | | return numberHandlingAttribute?.UnmappedMemberHandling; |
| | 0 | 749 | | } |
| | | 750 | | |
| | | 751 | | private static bool PropertyIsOverriddenAndIgnored(PropertyInfo propertyInfo, Dictionary<string, JsonPropertyInf |
| | 0 | 752 | | { |
| | 0 | 753 | | return propertyInfo.IsVirtual() && |
| | 0 | 754 | | ignoredMembers?.TryGetValue(propertyInfo.Name, out JsonPropertyInfo? ignoredMember) == true && |
| | 0 | 755 | | ignoredMember.IsVirtual && |
| | 0 | 756 | | propertyInfo.PropertyType == ignoredMember.PropertyType; |
| | 0 | 757 | | } |
| | | 758 | | |
| | | 759 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 760 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 761 | | private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo, NullabilityInfoContext nullabilityCtx) |
| | 0 | 762 | | { |
| | 0 | 763 | | Debug.Assert(typeInfo.Converter.ConstructorInfo != null); |
| | 0 | 764 | | ParameterInfo[] parameters = typeInfo.Converter.ConstructorInfo.GetParameters(); |
| | | 765 | | |
| | | 766 | | // Count non-out parameters - out parameters don't receive values from JSON. |
| | 0 | 767 | | int nonOutParameterCount = 0; |
| | 0 | 768 | | foreach (ParameterInfo param in parameters) |
| | 0 | 769 | | { |
| | 0 | 770 | | if (!param.IsOut) |
| | 0 | 771 | | { |
| | 0 | 772 | | nonOutParameterCount++; |
| | 0 | 773 | | } |
| | 0 | 774 | | } |
| | | 775 | | |
| | 0 | 776 | | JsonParameterInfoValues[] jsonParameters = new JsonParameterInfoValues[nonOutParameterCount]; |
| | | 777 | | |
| | 0 | 778 | | int jsonParamIndex = 0; |
| | 0 | 779 | | for (int i = 0; i < parameters.Length; i++) |
| | 0 | 780 | | { |
| | 0 | 781 | | ParameterInfo reflectionInfo = parameters[i]; |
| | | 782 | | |
| | | 783 | | // Skip out parameters - they don't receive values from JSON deserialization. |
| | 0 | 784 | | if (reflectionInfo.IsOut) |
| | 0 | 785 | | { |
| | 0 | 786 | | continue; |
| | | 787 | | } |
| | | 788 | | |
| | | 789 | | // Trimmed parameter names are reported as null in CoreCLR or "" in Mono. |
| | 0 | 790 | | if (string.IsNullOrEmpty(reflectionInfo.Name)) |
| | 0 | 791 | | { |
| | 0 | 792 | | Debug.Assert(typeInfo.Converter.ConstructorInfo.DeclaringType != null); |
| | 0 | 793 | | ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(typeInfo.Converter.Cons |
| | | 794 | | } |
| | | 795 | | |
| | | 796 | | // For byref parameters (in/ref), use the underlying element type. |
| | 0 | 797 | | Type parameterType = reflectionInfo.ParameterType; |
| | 0 | 798 | | if (parameterType.IsByRef) |
| | 0 | 799 | | { |
| | 0 | 800 | | parameterType = parameterType.GetElementType()!; |
| | 0 | 801 | | } |
| | | 802 | | |
| | 0 | 803 | | JsonParameterInfoValues jsonInfo = new() |
| | 0 | 804 | | { |
| | 0 | 805 | | Name = reflectionInfo.Name, |
| | 0 | 806 | | ParameterType = parameterType, |
| | 0 | 807 | | Position = jsonParamIndex, // Use the position in the args array, not the constructor parameter inde |
| | 0 | 808 | | HasDefaultValue = reflectionInfo.HasDefaultValue, |
| | 0 | 809 | | DefaultValue = reflectionInfo.GetDefaultValue(), |
| | 0 | 810 | | IsNullable = DetermineParameterNullability(reflectionInfo, nullabilityCtx) is not NullabilityState.N |
| | 0 | 811 | | }; |
| | | 812 | | |
| | 0 | 813 | | jsonParameters[jsonParamIndex] = jsonInfo; |
| | 0 | 814 | | jsonParamIndex++; |
| | 0 | 815 | | } |
| | | 816 | | |
| | 0 | 817 | | typeInfo.PopulateParameterInfoValues(jsonParameters); |
| | 0 | 818 | | } |
| | | 819 | | |
| | | 820 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 821 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 822 | | private static void PopulatePropertyInfo( |
| | | 823 | | JsonPropertyInfo jsonPropertyInfo, |
| | | 824 | | MemberInfo memberInfo, |
| | | 825 | | JsonConverter? customConverter, |
| | | 826 | | JsonIgnoreCondition? ignoreCondition, |
| | | 827 | | NullabilityInfoContext nullabilityCtx, |
| | | 828 | | bool shouldCheckForRequiredKeyword, |
| | | 829 | | bool hasJsonIncludeAttribute, |
| | | 830 | | JsonNamingPolicy? typeNamingPolicy) |
| | 0 | 831 | | { |
| | 0 | 832 | | Debug.Assert(jsonPropertyInfo.AttributeProvider is null); |
| | | 833 | | |
| | 0 | 834 | | switch (jsonPropertyInfo.AttributeProvider = memberInfo) |
| | | 835 | | { |
| | | 836 | | case PropertyInfo propertyInfo: |
| | 0 | 837 | | jsonPropertyInfo.MemberName = propertyInfo.Name; |
| | 0 | 838 | | jsonPropertyInfo.IsVirtual = propertyInfo.IsVirtual(); |
| | 0 | 839 | | jsonPropertyInfo.MemberType = MemberTypes.Property; |
| | 0 | 840 | | break; |
| | | 841 | | case FieldInfo fieldInfo: |
| | 0 | 842 | | jsonPropertyInfo.MemberName = fieldInfo.Name; |
| | 0 | 843 | | jsonPropertyInfo.MemberType = MemberTypes.Field; |
| | 0 | 844 | | break; |
| | | 845 | | default: |
| | 0 | 846 | | Debug.Fail("Only FieldInfo and PropertyInfo members are supported."); |
| | | 847 | | break; |
| | | 848 | | } |
| | | 849 | | |
| | 0 | 850 | | jsonPropertyInfo.CustomConverter = customConverter; |
| | 0 | 851 | | DeterminePropertyPolicies(jsonPropertyInfo, memberInfo); |
| | 0 | 852 | | DeterminePropertyName(jsonPropertyInfo, memberInfo, typeNamingPolicy); |
| | 0 | 853 | | DeterminePropertyIsRequired(jsonPropertyInfo, memberInfo, shouldCheckForRequiredKeyword); |
| | 0 | 854 | | DeterminePropertyNullability(jsonPropertyInfo, memberInfo, nullabilityCtx); |
| | | 855 | | |
| | 0 | 856 | | if (ignoreCondition != JsonIgnoreCondition.Always) |
| | 0 | 857 | | { |
| | 0 | 858 | | jsonPropertyInfo.DetermineReflectionPropertyAccessors(memberInfo, useNonPublicAccessors: hasJsonIncludeA |
| | 0 | 859 | | } |
| | | 860 | | |
| | 0 | 861 | | jsonPropertyInfo.IgnoreCondition = ignoreCondition; |
| | 0 | 862 | | jsonPropertyInfo.IsExtensionData = memberInfo.GetCustomAttribute<JsonExtensionDataAttribute>(inherit: false) |
| | 0 | 863 | | } |
| | | 864 | | |
| | | 865 | | private static void DeterminePropertyPolicies(JsonPropertyInfo propertyInfo, MemberInfo memberInfo) |
| | 0 | 866 | | { |
| | 0 | 867 | | JsonPropertyOrderAttribute? orderAttr = memberInfo.GetCustomAttribute<JsonPropertyOrderAttribute>(inherit: f |
| | 0 | 868 | | propertyInfo.Order = orderAttr?.Order ?? 0; |
| | | 869 | | |
| | 0 | 870 | | JsonNumberHandlingAttribute? numberHandlingAttr = memberInfo.GetCustomAttribute<JsonNumberHandlingAttribute> |
| | 0 | 871 | | propertyInfo.NumberHandling = numberHandlingAttr?.Handling; |
| | | 872 | | |
| | 0 | 873 | | JsonObjectCreationHandlingAttribute? objectCreationHandlingAttr = memberInfo.GetCustomAttribute<JsonObjectCr |
| | 0 | 874 | | propertyInfo.ObjectCreationHandling = objectCreationHandlingAttr?.Handling; |
| | 0 | 875 | | } |
| | | 876 | | |
| | | 877 | | private static void DeterminePropertyName(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, JsonNamingPolicy |
| | 0 | 878 | | { |
| | 0 | 879 | | JsonPropertyNameAttribute? nameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(inherit: |
| | | 880 | | string? name; |
| | 0 | 881 | | if (nameAttribute is not null) |
| | 0 | 882 | | { |
| | 0 | 883 | | name = nameAttribute.Name; |
| | 0 | 884 | | } |
| | | 885 | | else |
| | 0 | 886 | | { |
| | 0 | 887 | | JsonNamingPolicy? effectivePolicy = memberInfo.GetCustomAttribute<JsonNamingPolicyAttribute>(inherit: fa |
| | 0 | 888 | | ?? typeNamingPolicy |
| | 0 | 889 | | ?? propertyInfo.Options.PropertyNamingPolicy; |
| | | 890 | | |
| | 0 | 891 | | name = effectivePolicy is not null |
| | 0 | 892 | | ? effectivePolicy.ConvertName(memberInfo.Name) |
| | 0 | 893 | | : memberInfo.Name; |
| | 0 | 894 | | } |
| | | 895 | | |
| | 0 | 896 | | if (name is null) |
| | 0 | 897 | | { |
| | 0 | 898 | | ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(propertyInfo); |
| | | 899 | | } |
| | | 900 | | |
| | 0 | 901 | | propertyInfo.Name = name; |
| | 0 | 902 | | } |
| | | 903 | | |
| | | 904 | | private static void DeterminePropertyIsRequired(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, bool shoul |
| | 0 | 905 | | { |
| | 0 | 906 | | propertyInfo.IsRequired = |
| | 0 | 907 | | memberInfo.GetCustomAttribute<JsonRequiredAttribute>(inherit: false) is not null |
| | 0 | 908 | | || (shouldCheckForRequiredKeyword && memberInfo.HasRequiredMemberAttribute()); |
| | 0 | 909 | | } |
| | | 910 | | |
| | | 911 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 912 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 913 | | internal static void DeterminePropertyAccessors<T>(JsonPropertyInfo<T> jsonPropertyInfo, MemberInfo memberInfo, |
| | 0 | 914 | | { |
| | 0 | 915 | | Debug.Assert(memberInfo is FieldInfo or PropertyInfo); |
| | | 916 | | |
| | 0 | 917 | | switch (memberInfo) |
| | | 918 | | { |
| | | 919 | | case PropertyInfo propertyInfo: |
| | 0 | 920 | | MethodInfo? getMethod = propertyInfo.GetMethod; |
| | 0 | 921 | | if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors)) |
| | 0 | 922 | | { |
| | 0 | 923 | | jsonPropertyInfo.Get = MemberAccessor.CreatePropertyGetter<T>(propertyInfo); |
| | 0 | 924 | | } |
| | | 925 | | |
| | 0 | 926 | | MethodInfo? setMethod = propertyInfo.SetMethod; |
| | 0 | 927 | | if (setMethod != null && (setMethod.IsPublic || useNonPublicAccessors)) |
| | 0 | 928 | | { |
| | 0 | 929 | | jsonPropertyInfo.Set = MemberAccessor.CreatePropertySetter<T>(propertyInfo); |
| | 0 | 930 | | } |
| | | 931 | | |
| | 0 | 932 | | break; |
| | | 933 | | |
| | | 934 | | case FieldInfo fieldInfo: |
| | 0 | 935 | | Debug.Assert(fieldInfo.IsPublic || useNonPublicAccessors); |
| | | 936 | | |
| | 0 | 937 | | jsonPropertyInfo.Get = MemberAccessor.CreateFieldGetter<T>(fieldInfo); |
| | | 938 | | |
| | 0 | 939 | | if (!fieldInfo.IsInitOnly) |
| | 0 | 940 | | { |
| | 0 | 941 | | jsonPropertyInfo.Set = MemberAccessor.CreateFieldSetter<T>(fieldInfo); |
| | 0 | 942 | | } |
| | | 943 | | |
| | 0 | 944 | | break; |
| | | 945 | | |
| | | 946 | | default: |
| | 0 | 947 | | Debug.Fail($"Invalid MemberInfo type: {memberInfo.MemberType}"); |
| | | 948 | | break; |
| | | 949 | | } |
| | 0 | 950 | | } |
| | | 951 | | |
| | | 952 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 953 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 954 | | private static Func<object>? DetermineCreateObjectDelegate(Type type, JsonConverter converter) |
| | 0 | 955 | | { |
| | 0 | 956 | | ConstructorInfo? defaultCtor = null; |
| | | 957 | | |
| | 0 | 958 | | if (converter.ConstructorInfo != null && !converter.ConstructorIsParameterized) |
| | 0 | 959 | | { |
| | | 960 | | // A parameterless constructor has been resolved by the converter |
| | | 961 | | // (e.g. it might be a non-public ctor with JsonConstructorAttribute). |
| | 0 | 962 | | defaultCtor = converter.ConstructorInfo; |
| | 0 | 963 | | } |
| | | 964 | | |
| | | 965 | | // Fall back to resolving any public constructors on the type. |
| | 0 | 966 | | defaultCtor ??= type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, binder: null, Type.EmptyTyp |
| | | 967 | | |
| | 0 | 968 | | return MemberAccessor.CreateParameterlessConstructor(type, defaultCtor); |
| | 0 | 969 | | } |
| | | 970 | | |
| | | 971 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 972 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 973 | | private static void DeterminePropertyNullability(JsonPropertyInfo propertyInfo, MemberInfo memberInfo, Nullabili |
| | 0 | 974 | | { |
| | 0 | 975 | | if (!propertyInfo.PropertyTypeCanBeNull) |
| | 0 | 976 | | { |
| | 0 | 977 | | return; |
| | | 978 | | } |
| | | 979 | | |
| | | 980 | | NullabilityInfo nullabilityInfo; |
| | 0 | 981 | | if (propertyInfo.MemberType is MemberTypes.Property) |
| | 0 | 982 | | { |
| | 0 | 983 | | nullabilityInfo = nullabilityCtx.Create((PropertyInfo)memberInfo); |
| | 0 | 984 | | } |
| | | 985 | | else |
| | 0 | 986 | | { |
| | 0 | 987 | | Debug.Assert(propertyInfo.MemberType is MemberTypes.Field); |
| | 0 | 988 | | nullabilityInfo = nullabilityCtx.Create((FieldInfo)memberInfo); |
| | 0 | 989 | | } |
| | | 990 | | |
| | 0 | 991 | | propertyInfo.IsGetNullable = nullabilityInfo.ReadState is not NullabilityState.NotNull; |
| | 0 | 992 | | propertyInfo.IsSetNullable = nullabilityInfo.WriteState is not NullabilityState.NotNull; |
| | 0 | 993 | | } |
| | | 994 | | |
| | | 995 | | [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] |
| | | 996 | | [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] |
| | | 997 | | private static NullabilityState DetermineParameterNullability(ParameterInfo parameterInfo, NullabilityInfoContex |
| | 0 | 998 | | { |
| | 0 | 999 | | if (!parameterInfo.ParameterType.IsNullableType()) |
| | 0 | 1000 | | { |
| | 0 | 1001 | | return NullabilityState.NotNull; |
| | | 1002 | | } |
| | | 1003 | | |
| | 0 | 1004 | | NullabilityInfo nullability = nullabilityCtx.Create(parameterInfo); |
| | 0 | 1005 | | return nullability.WriteState; |
| | 0 | 1006 | | } |
| | | 1007 | | } |
| | | 1008 | | } |
| | | 1009 | | |