diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 9348208f6d8be..ef169c7a87510 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -645,7 +645,7 @@ Runtime type '{0}' has a diamond ambiguity between derived types '{1}' and '{2}' of polymorphic type '{3}'. Consider either removing one of the derived types or removing the 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' setting. - Invalid JsonTypeInfo operation for metadata kind '{0}'. + Invalid JsonTypeInfo operation for JsonTypeInfoKind '{0}'. One of the provided resolvers is null. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index f372d8bd8336e..d28e826b8b3c3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -28,6 +28,8 @@ public sealed partial class JsonSerializerOptions /// /// The type to resolve contract metadata for. /// The contract metadata resolved for . + /// is . + /// is not valid for serialization. /// /// Returned metadata can be downcast to and used with the relevant overloads. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 1a45a87807437..63ce1d606fe8f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -175,12 +175,14 @@ public JsonSerializerOptions(JsonSerializerDefaults defaults) : this() } /// - /// Gets or sets a contract resolver. + /// Gets or sets the contract resolver used by this instance. /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// /// - /// When used with the reflection-based APIs, - /// a setting be equivalent to and replaced by the reflection-based - /// . + /// A setting is equivalent to using the reflection-based . + /// The property will be populated automatically once used with one of the methods. /// public IJsonTypeInfoResolver? TypeInfoResolver { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs index 01cd5430ee97e..e07908901f07f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs @@ -10,14 +10,17 @@ namespace System.Text.Json.Serialization.Metadata { /// - /// Default JsonTypeInfo resolver. + /// Defines the default, reflection-based JSON contract resolver used by System.Text.Json. /// + /// + /// The contract resolver used by . + /// public partial class DefaultJsonTypeInfoResolver : IJsonTypeInfoResolver { private bool _mutable; /// - /// Constructs DefaultJsonTypeInfoResolver. + /// Creates a mutable instance. /// [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] @@ -35,7 +38,17 @@ private DefaultJsonTypeInfoResolver(bool mutable) s_defaultSimpleConverters ??= GetDefaultSimpleConverters(); } - /// + /// + /// Resolves a JSON contract for a given and configuration. + /// + /// The type for which to resolve a JSON contract. + /// A instance used to determine contract configuration. + /// A defining a reflection-derived JSON contract for . + /// or is . + /// + /// The base implementation of this method will produce a reflection-derived contract + /// and apply any callbacks from the list. + /// [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The ctor is marked RequiresUnreferencedCode.")] [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", @@ -88,9 +101,13 @@ private static JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOpt private static MethodInfo? s_createReflectionJsonTypeInfoMethodInfo; /// - /// List of JsonTypeInfo modifiers. Modifying callbacks are called consecutively after initial resolution - /// and cannot be changed after GetTypeInfo is called. + /// Gets a list of user-defined callbacks that can be used to modify the initial contract. /// + /// + /// The modifier list will be rendered immutable after the first invocation. + /// + /// Modifier callbacks are called consecutively in the order in which they are specified in the list. + /// public IList> Modifiers => _modifiers ??= new ModifierCollection(this); private ModifierCollection? _modifiers; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs index 22f74387153d4..4acabe53d731f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs @@ -1,24 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - namespace System.Text.Json.Serialization.Metadata { /// - /// Exposes method for resolving Type into JsonTypeInfo for given options. + /// Used to resolve the JSON serialization contract for requested types. /// public interface IJsonTypeInfoResolver { /// - /// Resolves Type into JsonTypeInfo which defines serialization and deserialization logic. + /// Resolves a contract for the requested type and options. /// /// Type to be resolved. - /// JsonSerializerOptions instance defining resolution parameters. - /// Returns JsonTypeInfo instance or null if the resolver cannot produce metadata for this type. + /// Configuration used when resolving the metadata. + /// + /// A instance matching the requested type, + /// or if no contract could be resolved. + /// JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs index f550e910b2708..d9d518b25273a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPolymorphismOptions.cs @@ -27,6 +27,9 @@ public class JsonPolymorphismOptions /// unrecognized type discriminator id's and reverts to the contract of the base type. /// Otherwise, it will fail the deserialization. /// + /// + /// The parent instance has been locked for further modification. + /// public bool IgnoreUnrecognizedTypeDiscriminators { get => _ignoreUnrecognizedTypeDiscriminators; @@ -40,6 +43,9 @@ public bool IgnoreUnrecognizedTypeDiscriminators /// /// Gets or sets the behavior when serializing an undeclared derived runtime type. /// + /// + /// The parent instance has been locked for further modification. + /// public JsonUnknownDerivedTypeHandling UnknownDerivedTypeHandling { get => _unknownDerivedTypeHandling; @@ -54,6 +60,9 @@ public JsonUnknownDerivedTypeHandling UnknownDerivedTypeHandling /// Gets or sets a custom type discriminator property name for the polymorhic type. /// Uses the default '$type' property name if left unset. /// + /// + /// The parent instance has been locked for further modification. + /// [AllowNull] public string TypeDiscriminatorPropertyName { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 1418afb476a22..bf0503ed518b6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization.Metadata { /// - /// Provides JSON serialization-related metadata about a property or field. + /// Provides JSON serialization-related metadata about a property or field defined in an object. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public abstract class JsonPropertyInfo @@ -28,8 +28,17 @@ public abstract class JsonPropertyInfo internal abstract JsonConverter EffectiveConverter { get; } /// - /// Custom converter override at the property level, equivalent to annotation. + /// Gets or sets a custom converter override for the current property. /// + /// + /// The instance has been locked for further modification. + /// + /// + /// It is possible to use instances with this property. + /// + /// For contracts originating from , the value of + /// will be mapped from annotations. + /// public JsonConverter? CustomConverter { get => _customConverter; @@ -45,6 +54,9 @@ public JsonConverter? CustomConverter /// /// Gets or sets a getter delegate for the property. /// + /// + /// The instance has been locked for further modification. + /// /// /// Setting to will result in the property being skipped on serialization. /// @@ -61,6 +73,9 @@ public JsonConverter? CustomConverter /// /// Gets or sets a setter delegate for the property. /// + /// + /// The instance has been locked for further modification. + /// /// /// Setting to will result in the property being skipped on deserialization. /// @@ -85,13 +100,16 @@ public JsonConverter? CustomConverter /// /// Gets or sets a predicate deciding whether the current property value should be serialized. /// + /// + /// The instance has been locked for further modification. + /// /// /// The first parameter denotes the parent object, the second parameter denotes the property value. /// /// Setting the predicate to is equivalent to always serializing the property value. /// - /// When serializing using , the value of - /// will map to this predicate. + /// For contracts originating from , + /// the value of will map to this predicate. /// public Func? ShouldSerialize { @@ -127,6 +145,9 @@ internal JsonIgnoreCondition? IgnoreCondition /// /// Gets or sets a custom attribute provider for the current property. /// + /// + /// The instance has been locked for further modification. + /// /// /// When resolving metadata via this /// will be populated with the underlying of the serialized property or field. @@ -153,9 +174,16 @@ public ICustomAttributeProvider? AttributeProvider /// /// Specifies whether the current property is a special extension data property. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// The current is not valid for use with extension data. + /// /// - /// Properties annotated with - /// will appear here when using or . + /// For contracts originating from or , + /// the value of this property will be mapped from annotations. /// public bool IsExtensionData { @@ -164,7 +192,7 @@ public bool IsExtensionData { VerifyMutable(); - if (value && !JsonTypeInfo.IsValidExtensionDataProperty(this)) + if (value && !JsonTypeInfo.IsValidExtensionDataProperty(PropertyType)) { ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(this); } @@ -199,7 +227,7 @@ internal static JsonPropertyInfo GetPropertyPlaceholder() } /// - /// Gets the type of the current property metadata. + /// Gets the type of the current property. /// public Type PropertyType { get; } @@ -537,16 +565,22 @@ internal bool IgnoreReadOnlyMember // There are 3 copies of the property name: // 1) Name. The unescaped property name. - // 2) NameAsUtf8Bytes. The Utf8 version of Name. Used during during deserialization for property lookup. + // 2) NameAsUtf8Bytes. The Utf8 version of Name. Used during deserialization for property lookup. // 3) EscapedNameSection. The escaped version of NameAsUtf8Bytes plus the wrapping quotes and a trailing colon. Used during serialization. /// /// Gets or sets the JSON property name used when serializing the property. /// + /// is null. + /// + /// The instance has been locked for further modification. + /// /// - /// This typically reflects the underlying .NET member name, - /// the name derived from , - /// or the value specified in , + /// The value of cannot conflict with that of other defined in the declaring . + /// + /// For contracts originating from or , + /// the value typically reflects the underlying .NET member name, the name derived from , + /// or the value specified in . /// public string Name { @@ -581,16 +615,19 @@ public string Name internal byte[] EscapedNameSection { get; set; } = null!; /// - /// Options associated with JsonPropertyInfo + /// Gets the value associated with the current contract instance. /// public JsonSerializerOptions Options { get; } /// /// Gets or sets the serialization order for the current property. /// + /// + /// The instance has been locked for further modification. + /// /// - /// When using , properties annotated - /// with the will map to this value. + /// For contracts originating from or , + /// the value of this property will be mapped from annotations. /// public int Order { @@ -752,8 +789,15 @@ internal JsonTypeInfo JsonTypeInfo internal JsonNumberHandling? DeclaringTypeNumberHandling { get; set; } /// - /// Number handling specific to this property, i.e. set by attribute + /// Gets or sets the applied to the current property. /// + /// + /// The instance has been locked for further modification. + /// + /// + /// For contracts originating from or , + /// the value of this property will be mapped from annotations. + /// public JsonNumberHandling? NumberHandling { get => _numberHandling; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 9d7dbb8a36db2..59fe6c80822ed 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -32,8 +32,22 @@ public abstract partial class JsonTypeInfo private Action? _onDeserialized; /// - /// Object constructor. If set to null type is not deserializable. + /// Gets or sets a parameterless factory to be used on deserialization. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// A parameterless factory is not supported for the current metadata . + /// + /// + /// If set to , any attempt to deserialize instances of the given type will result in an exception. + /// + /// For contracts originating from or , + /// types with a single default constructor or default constructors annotated with + /// will be mapped to this delegate. + /// public Func? CreateObject { get => _createObject; @@ -51,8 +65,16 @@ public Func? CreateObject /// /// Gets or sets a callback to be invoked before serialization occurs. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// Serialization callbacks are only supported for metadata. + /// /// - /// Types implementing will map to this callback. + /// For contracts originating from or , + /// the value of this callback will be mapped from any implementation on the type. /// public Action? OnSerializing { @@ -73,8 +95,16 @@ public Action? OnSerializing /// /// Gets or sets a callback to be invoked after serialization occurs. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// Serialization callbacks are only supported for metadata. + /// /// - /// Types implementing will map to this callback. + /// For contracts originating from or , + /// the value of this callback will be mapped from any implementation on the type. /// public Action? OnSerialized { @@ -95,8 +125,16 @@ public Action? OnSerialized /// /// Gets or sets a callback to be invoked before deserialization occurs. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// Serialization callbacks are only supported for metadata. + /// /// - /// Types implementing will map to this callback. + /// For contracts originating from or , + /// the value of this callback will be mapped from any implementation on the type. /// public Action? OnDeserializing { @@ -117,8 +155,16 @@ public Action? OnDeserializing /// /// Gets or sets a callback to be invoked after deserialization occurs. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// Serialization callbacks are only supported for metadata. + /// /// - /// Types implementing will map to this callback. + /// For contracts originating from or , + /// the value of this callback will be mapped from any implementation on the type. /// public Action? OnDeserialized { @@ -137,8 +183,19 @@ public Action? OnDeserialized } /// - /// Gets JsonPropertyInfo list. Only applicable when equals . + /// Gets the list of metadata corresponding to the current type. /// + /// + /// Property is only applicable to metadata of kind . + /// For other kinds an empty, read-only list will be returned. + /// + /// The order of entries in the list determines the serialization order, + /// unless either of the entries specifies a non-zero value, + /// in which case the properties will be stable sorted by . + /// + /// It is required that added entries are unique up to , + /// however this will only be validated on serialization, once the metadata instance gets locked for further modification. + /// public IList Properties { get @@ -155,9 +212,19 @@ public IList Properties /// /// Gets or sets a configuration object specifying polymorphism metadata. /// + /// + /// has been associated with a different instance. + /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// Polymorphic serialization is not supported for the current metadata . + /// /// - /// Configuration specified in the and - /// will automatically map to this property. + /// For contracts originating from or , + /// the configuration of this setting will be mapped from any or annotations. /// public JsonPolymorphismOptions? PolymorphismOptions { @@ -168,6 +235,11 @@ public JsonPolymorphismOptions? PolymorphismOptions if (value != null) { + if (Kind == JsonTypeInfoKind.None) + { + ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind); + } + if (value.DeclaringTypeInfo != null && value.DeclaringTypeInfo != this) { ThrowHelper.ThrowArgumentException_JsonPolymorphismOptionsAssociatedWithDifferentJsonTypeInfo(nameof(value)); @@ -180,7 +252,7 @@ public JsonPolymorphismOptions? PolymorphismOptions } } - private JsonPolymorphismOptions? _polymorphismOptions; + private protected JsonPolymorphismOptions? _polymorphismOptions; internal object? CreateObjectWithArgs { get; set; } @@ -290,23 +362,38 @@ internal JsonTypeInfo? KeyTypeInfo internal Type? KeyType { get; } /// - /// Gets the instance associated with . + /// Gets the value associated with the current instance. /// public JsonSerializerOptions Options { get; } /// - /// Type associated with JsonTypeInfo + /// Gets the for which the JSON serialization contract is being defined. /// public Type Type { get; } /// - /// Converter associated with the type for the given options instance + /// Gets the associated with the current type. /// + /// + /// The associated with the type determines the value of , + /// and by extension the types of metadata that are configurable in the current JSON contract. + /// As such, the value of the converter cannot be changed once a instance has been created. + /// public JsonConverter Converter { get; } /// - /// Determines the kind of contract metadata current JsonTypeInfo instance is customizing + /// Determines the kind of contract metadata that the current instance is specifying. /// + /// + /// The value of determines what aspects of the JSON contract are configurable. + /// For example, it is only possible to configure the list for metadata + /// of kind . + /// + /// The value of is determined exclusively by the + /// resolved for the current type, and cannot be changed once resolution has happened. + /// User-defined custom converters (specified either via or ) + /// are metadata-agnostic and thus always resolve to . + /// public JsonTypeInfoKind Kind { get; private set; } /// @@ -338,8 +425,12 @@ internal JsonTypeInfo? KeyTypeInfo /// /// Gets or sets the type-level override. /// + /// + /// The instance has been locked for further modification. + /// /// - /// For it is equivalent to annotating the property with . + /// For contracts originating from or , + /// the value of this callback will be mapped from any annotations. /// public JsonNumberHandling? NumberHandling { @@ -900,16 +991,12 @@ private static bool IsByRefLike(Type type) #endif } - internal static bool IsValidExtensionDataProperty(JsonPropertyInfo jsonPropertyInfo) + internal static bool IsValidExtensionDataProperty(Type propertyType) { - Type memberType = jsonPropertyInfo.PropertyType; - - bool typeIsValid = typeof(IDictionary).IsAssignableFrom(memberType) || - typeof(IDictionary).IsAssignableFrom(memberType) || + return typeof(IDictionary).IsAssignableFrom(propertyType) || + typeof(IDictionary).IsAssignableFrom(propertyType) || // Avoid a reference to typeof(JsonNode) to support trimming. - (memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, typeof(JsonTypeInfo).Assembly)); - - return typeIsValid; + (propertyType.FullName == JsonObjectTypeName && ReferenceEquals(propertyType.Assembly, typeof(JsonTypeInfo).Assembly)); } internal JsonPropertyDictionary CreatePropertyCache(int capacity) @@ -938,7 +1025,7 @@ private static JsonParameterInfo CreateConstructorParameter( private static JsonTypeInfoKind GetTypeInfoKind(Type type, ConverterStrategy converterStrategy) { - // System.Object is semi-polimorphic and will not respect Properties + // System.Object is polymorphic and will not respect Properties if (type == typeof(object)) { return JsonTypeInfoKind.None; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoKind.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoKind.cs index 200773d83218f..fcf1978852642 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoKind.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoKind.cs @@ -4,24 +4,24 @@ namespace System.Text.Json.Serialization.Metadata { /// - /// Determines the kind of contract metadata a given JsonTypeInfo instance is customizing + /// Determines the kind of contract metadata a given is specifying. /// public enum JsonTypeInfoKind { /// - /// Type is either a primitive value or uses a custom converter. JsonTypeInfo metadata does not apply here. + /// Type is either a simple value or uses a custom converter. /// None = 0, /// - /// Type is serialized as object with properties + /// Type is serialized as an object with properties. /// Object = 1, /// - /// Type is serialized as a collection with elements + /// Type is serialized as a collection with elements. /// Enumerable = 2, /// - /// Type is serialized as a dictionary with key/value pair entries + /// Type is serialized as a dictionary with key/value pair entries. /// Dictionary = 3 } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 0b069b4145b3e..95ac5c21a34ae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -17,8 +17,22 @@ public abstract class JsonTypeInfo : JsonTypeInfo private Func? _typedCreateObject; /// - /// Function for creating object before properties are set. If set to null type is not deserializable. + /// Gets or sets a parameterless factory to be used on deserialization. /// + /// + /// The instance has been locked for further modification. + /// + /// -or- + /// + /// A parameterless factory is not supported for the current metadata . + /// + /// + /// If set to , any attempt to deserialize instances of the given type will fail at runtime. + /// + /// For contracts originating from or , + /// types with a single default constructor or default constructors annotated with + /// will be mapped to this delegate. + /// public new Func? CreateObject { get => _typedCreateObject; @@ -109,6 +123,16 @@ private protected override JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo }; } + private protected void PopulatePolymorphismMetadata() + { + JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); + if (options != null) + { + options.DeclaringTypeInfo = this; + _polymorphismOptions = options; + } + } + private protected void MapInterfaceTypesToCallbacks() { // Callbacks currently only supported in object kinds diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolver.cs index 9c111cd516607..f7537104442ea 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoResolver.cs @@ -1,26 +1,27 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - namespace System.Text.Json.Serialization.Metadata { /// - /// Contains utilities for IJsonTypeInfoResolver + /// Contains utilities and combinators acting on . /// public static class JsonTypeInfoResolver { /// - /// Combines multiple IJsonTypeInfoResolvers + /// Combines multiple sources into one. /// - /// - /// + /// Sequence of contract resolvers to be queried for metadata. + /// A combining results from . + /// or any of its elements is null. /// - /// All resolvers except last one should return null when they do not know how to create JsonTypeInfo for a given type. - /// Last resolver on the list should return non-null for most of the types unless explicit type blocking is desired. + /// The combined resolver will query each of in the specified order, + /// returning the first result that is non-null. If all return null, + /// then the combined resolver will also return . + /// + /// Can be used to combine multiple sources, + /// which typically define contract metadata for small subsets of types. + /// It can also be used to fall back to wherever necessary. /// public static IJsonTypeInfoResolver Combine(params IJsonTypeInfoResolver[] resolvers) { @@ -29,7 +30,7 @@ public static IJsonTypeInfoResolver Combine(params IJsonTypeInfoResolver[] resol throw new ArgumentNullException(nameof(resolvers)); } - foreach (var resolver in resolvers) + foreach (IJsonTypeInfoResolver? resolver in resolvers) { if (resolver == null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs index 042493e5806b7..3397577829a83 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs @@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json.Reflection; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json.Serialization.Metadata { @@ -21,7 +20,7 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o : base(converter, options) { NumberHandling = GetNumberHandlingForType(Type); - PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); + PopulatePolymorphismMetadata(); MapInterfaceTypesToCallbacks(); if (PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs index 0dcca397deba8..c50b4d0b8e4a4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs @@ -20,7 +20,7 @@ internal sealed class SourceGenJsonTypeInfo : JsonTypeInfo public SourceGenJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options) : base(converter, options) { - PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); + PopulatePolymorphismMetadata(); MapInterfaceTypesToCallbacks(); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs index b16720a6c820c..98bba98a10a87 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonPolymorphismOptionsTests.cs @@ -52,6 +52,16 @@ public static IEnumerable GetDerivedTypes() static object[] WrapArgs(params JsonDerivedType[] derivedTypes) => new object[] { derivedTypes }; } + [Fact] + public static void JsonPolymorphismOptions_AssigningOptionsToJsonTypeInfoKindNone_ThrowsInvalidOperationException() + { + var options = new JsonPolymorphismOptions(); + JsonTypeInfo jti = JsonTypeInfo.CreateJsonTypeInfo(typeof(int), new()); + Assert.Equal(JsonTypeInfoKind.None, jti.Kind); + + Assert.Throws(() => jti.PolymorphismOptions = options); + } + [Fact] public static void JsonPolymorphismOptions_AssigningOptionsToSecondJsonTypeInfo_ThrowsInvalidOperationException() {