diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index fe799d389161e..514dd8cd5852f 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -339,7 +339,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults defaults) { public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { } public bool AllowTrailingCommas { get { throw null; } set { } } public System.Collections.Generic.IList Converters { get { throw null; } } - public static System.Text.Json.JsonSerializerOptions Default { get { throw null; } } + public static System.Text.Json.JsonSerializerOptions Default { [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."), System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] get { throw null; } } public int DefaultBufferSize { get { throw null; } set { } } public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } } public System.Text.Json.JsonNamingPolicy? DictionaryKeyPolicy { get { throw null; } set { } } @@ -356,16 +356,13 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { } public System.Text.Json.JsonNamingPolicy? PropertyNamingPolicy { get { throw null; } set { } } public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } } public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } } - [System.Diagnostics.CodeAnalysis.AllowNullAttribute] - public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver TypeInfoResolver { [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."), System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] get { throw null; } set { } } + public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? TypeInfoResolver { get { throw null; } set { } } public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } public void AddContext() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Getting a converter for a type may require reflection which depends on runtime code generation.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Getting a converter for a type may require reflection which depends on unreferenced code.")] public System.Text.Json.Serialization.JsonConverter GetConverter(System.Type typeToConvert) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Getting a metadata for a type may require reflection which depends on unreferenced code.")] - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Getting a metadata for a type may require reflection which depends on runtime code generation.")] public System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(System.Type type) { throw null; } } public enum JsonTokenType : byte diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 34166a7016ee3..f08e3e8e57b4b 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -585,7 +585,7 @@ Built-in type converters have not been initialized. There is no converter available for type '{0}'. To root all built-in converters, use a 'JsonSerializerOptions'-based method of the 'JsonSerializer'. - Metadata for type '{0}' was not provided to the serializer. The serializer method used does not support reflection-based creation of serialization-related type metadata. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically. + Metadata for type '{0}' was not provided by TypeInfoResolver of type '{1}'. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically. Collection is read-only. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs index fef9b4873608d..bcd88d9bc6f1d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs @@ -165,7 +165,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio CreateNodes(); Debug.Assert(_list != null); - options ??= JsonSerializerOptions.Default; + options ??= s_defaultOptions; writer.WriteStartArray(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs index 2abaaf05ce5c2..ed716927856a8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs @@ -5,6 +5,9 @@ namespace System.Text.Json.Nodes { public abstract partial class JsonNode { + // linker-safe default JsonSerializerOptions instance used by JsonNode methods. + private protected readonly JsonSerializerOptions s_defaultOptions = new(); + /// /// Converts the current instance to string in JSON format. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs index 898e68f3c6369..b1d711242773d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs @@ -96,7 +96,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio } else { - options ??= JsonSerializerOptions.Default; + options ??= s_defaultOptions; writer.WriteStartObject(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueTrimmable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueTrimmable.cs index 32d6fea5407ac..5f5a354ed12f4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueTrimmable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueTrimmable.cs @@ -34,7 +34,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio if (_converter != null) { - options ??= JsonSerializerOptions.Default; + options ??= s_defaultOptions; if (_converter.IsInternalConverterForNumberType) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs index 73e25b73fd2b6..7ce62fe8bafb5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs @@ -20,7 +20,11 @@ private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options, Type run Debug.Assert(runtimeType != null); options ??= JsonSerializerOptions.Default; - options.InitializeForReflectionSerializer(); + + if (!options.IsLockedInstance || !DefaultJsonTypeInfoResolver.IsDefaultInstanceRooted) + { + options.InitializeForReflectionSerializer(); + } return options.GetTypeInfoForRootType(runtimeType); } @@ -33,7 +37,7 @@ private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type type JsonTypeInfo? info = context.GetTypeInfo(type); if (info is null) { - ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type, context); } return info; 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 7ad272ab340dc..c0809f187894d 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 @@ -33,8 +33,6 @@ public sealed partial class JsonSerializerOptions /// /// If the instance is locked for modification, the method will return a cached instance for the metadata. /// - [RequiresUnreferencedCode("Getting a metadata for a type may require reflection which depends on unreferenced code.")] - [RequiresDynamicCode("Getting a metadata for a type may require reflection which depends on runtime code generation.")] public JsonTypeInfo GetTypeInfo(Type type) { if (type is null) @@ -47,8 +45,6 @@ public JsonTypeInfo GetTypeInfo(Type type) ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null); } - _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance(); - JsonTypeInfo? typeInfo; if (IsLockedInstance) { @@ -62,7 +58,7 @@ public JsonTypeInfo GetTypeInfo(Type type) if (typeInfo is null) { - ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type); + ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver); } return typeInfo; @@ -82,7 +78,7 @@ internal JsonTypeInfo GetTypeInfoCached(Type type) if (typeInfo == null) { - ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type); + ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver); return null; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index cfef45c315620..c28edbdaf3fa4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -46,7 +46,13 @@ public JsonConverter GetConverter(Type typeToConvert) ThrowHelper.ThrowArgumentNullException(nameof(typeToConvert)); } - _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance(); + if (_typeInfoResolver is null) + { + // Backward compatibility -- root the default reflection converters + // but do not populate the TypeInfoResolver setting. + DefaultJsonTypeInfoResolver.RootDefaultInstance(); + } + return GetConverterFromTypeInfo(typeToConvert); } 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 17e88162bf148..1a45a87807437 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 @@ -10,6 +10,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; +using System.Threading; namespace System.Text.Json { @@ -32,7 +33,22 @@ public sealed partial class JsonSerializerOptions /// so using fresh default instances every time one is needed can result in redundant recomputation of converters. /// This property provides a shared instance that can be consumed by any number of components without necessitating any converter recomputation. /// - public static JsonSerializerOptions Default { get; } = CreateDefaultImmutableInstance(); + public static JsonSerializerOptions Default + { + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] + get + { + if (s_defaultOptions is not JsonSerializerOptions options) + { + options = GetOrCreateDefaultOptionsInstance(); + } + + return options; + } + } + + private static JsonSerializerOptions? s_defaultOptions; // For any new option added, adding it to the options copied in the copy constructor below must be considered. private IJsonTypeInfoResolver? _typeInfoResolver; @@ -162,16 +178,15 @@ public JsonSerializerOptions(JsonSerializerDefaults defaults) : this() /// Gets or sets a contract resolver. /// /// - /// A setting is equivalent to using the reflection-based . + /// When used with the reflection-based APIs, + /// a setting be equivalent to and replaced by the reflection-based + /// . /// - [AllowNull] - public IJsonTypeInfoResolver TypeInfoResolver + public IJsonTypeInfoResolver? TypeInfoResolver { - [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] get { - return _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance(); + return _typeInfoResolver; } set { @@ -180,9 +195,6 @@ public IJsonTypeInfoResolver TypeInfoResolver } } - // Needed since public property is RequiresUnreferencedCode. - internal IJsonTypeInfoResolver? TypeInfoResolverSafe => _typeInfoResolver; - /// /// Defines whether an extra comma at the end of a list of JSON values in an object or array /// is allowed (and ignored) within the JSON payload being deserialized. @@ -598,6 +610,7 @@ internal bool IsLockedInstance set { Debug.Assert(value, "cannot unlock options instances"); + Debug.Assert(_typeInfoResolver != null, "cannot lock without a resolver."); _isLockedInstance = true; } } @@ -609,43 +622,22 @@ internal bool IsLockedInstance [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] internal void InitializeForReflectionSerializer() { - if (!_isInitializedForReflectionSerializer) - { - DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance(); - _typeInfoResolver ??= defaultResolver; - IsLockedInstance = true; - - CachingContext? context = GetCachingContext(); - Debug.Assert(context != null); - - if (context.Options != this) - { - // We're using a shared caching context deriving from a different options instance; - // for coherence ensure that it has been opted in for reflection-based serialization as well. - context.Options.InitializeForReflectionSerializer(); - } - - _isInitializedForReflectionSerializer = true; - } + // Even if a resolver has already been specified, we need to root + // the default resolver to gain access to the default converters. + DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance(); + _typeInfoResolver ??= defaultResolver; + IsLockedInstance = true; } - private volatile bool _isInitializedForReflectionSerializer; - internal void InitializeForMetadataGeneration() { - if (!_isInitializedForMetadataGeneration) + if (_typeInfoResolver is null) { - if (_typeInfoResolver is null) - { - ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoUsedButTypeInfoResolverNotSet(); - } - - IsLockedInstance = true; - _isInitializedForMetadataGeneration = true; + ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoUsedButTypeInfoResolverNotSet(); } - } - private volatile bool _isInitializedForMetadataGeneration; + IsLockedInstance = true; + } private JsonTypeInfo? GetTypeInfoNoCaching(Type type) { @@ -730,10 +722,17 @@ public ConverterList(JsonSerializerOptions options, IList? source protected override void VerifyMutable() => _options.VerifyMutable(); } - private static JsonSerializerOptions CreateDefaultImmutableInstance() + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] + private static JsonSerializerOptions GetOrCreateDefaultOptionsInstance() { - var options = new JsonSerializerOptions { IsLockedInstance = true }; - return options; + var options = new JsonSerializerOptions + { + TypeInfoResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance(), + IsLockedInstance = true + }; + + return Interlocked.CompareExchange(ref s_defaultOptions, options, null) ?? options; } } } 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 2e2e7ef1e2697..4b4990818e1ce 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 @@ -118,7 +118,7 @@ protected override void VerifyMutable() } } - internal static DefaultJsonTypeInfoResolver? DefaultInstance => s_defaultInstance; + internal static bool IsDefaultInstanceRooted => s_defaultInstance is not null; private static DefaultJsonTypeInfoResolver? s_defaultInstance; [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] 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 689ea6bedf2eb..b08e40a5af731 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 @@ -205,7 +205,7 @@ internal void ValidateCanBeUsedForDeserialization() { if (ThrowOnDeserialize) { - ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolverSafe, Type); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type); } } @@ -434,7 +434,11 @@ void ConfigureLocked() internal virtual void Configure() { Debug.Assert(Monitor.IsEntered(_configureLock), "Configure called directly, use EnsureConfigured which locks this method"); - Options.InitializeForMetadataGeneration(); + + if (!Options.IsLockedInstance) + { + Options.InitializeForMetadataGeneration(); + } PropertyInfoForTypeInfo.EnsureChildOf(this); PropertyInfoForTypeInfo.EnsureConfigured(); 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 139cd781cd631..0dcca397deba8 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 @@ -94,7 +94,7 @@ internal override JsonParameterInfoValues[] GetParameterInfoValues() JsonParameterInfoValues[] array; if (CtorParamInitFunc == null || (array = CtorParamInitFunc()) == null) { - ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeCtorParams(Options.TypeInfoResolverSafe, Type); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeCtorParams(Options.TypeInfoResolver, Type); return null!; } @@ -132,7 +132,7 @@ internal override void LateAddProperties() return; } - ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolverSafe, Type); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type); return; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 3719e49c03110..508ff39e20564 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -651,15 +651,15 @@ public static void ThrowNotSupportedException_BuiltInConvertersNotRooted(Type ty } [DoesNotReturn] - public static void ThrowNotSupportedException_NoMetadataForType(Type type) + public static void ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver? resolver) { - throw new NotSupportedException(SR.Format(SR.NoMetadataForType, type)); + throw new NotSupportedException(SR.Format(SR.NoMetadataForType, type, resolver?.GetType().FullName ?? "")); } [DoesNotReturn] - public static void ThrowInvalidOperationException_NoMetadataForType(Type type) + public static void ThrowInvalidOperationException_NoMetadataForType(Type type, IJsonTypeInfoResolver? resolver) { - throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type)); + throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type, resolver?.GetType().FullName ?? "")); } [DoesNotReturn] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs index c45b2a2e217dd..3e8403547d722 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonPropertyInfo.cs @@ -18,7 +18,7 @@ public static partial class DefaultJsonTypeInfoResolverTests [Fact] public static void JsonPropertyInfoOptionsAreSet() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(typeof(MyClass), options); CreatePropertyAndCheckOptions(options, typeInfo); @@ -51,7 +51,7 @@ public static void JsonPropertyInfoPropertyTypeIsSetWhenUsingCreateJsonPropertyI [Fact] public static void JsonPropertyInfoPropertyTypeIsSet() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(MyClass), options); Assert.Equal(2, typeInfo.Properties.Count); JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; @@ -79,7 +79,7 @@ public static void JsonPropertyInfoNameIsSetAndIsMutableWhenUsingCreateJsonPrope [Fact] public static void JsonPropertyInfoNameIsSetAndIsMutableForDefaultResolver() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(MyClass), options); Assert.Equal(2, typeInfo.Properties.Count); JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; @@ -95,7 +95,7 @@ public static void JsonPropertyInfoNameIsSetAndIsMutableForDefaultResolver() [Fact] public static void JsonPropertyInfoForDefaultResolverHasNamingPoliciesRulesApplied() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(MyClass), options); Assert.Equal(2, typeInfo.Properties.Count); @@ -111,7 +111,7 @@ public static void JsonPropertyInfoForDefaultResolverHasNamingPoliciesRulesAppli [Fact] public static void JsonPropertyInfoCustomConverterIsNullWhenUsingCreateJsonPropertyInfo() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = new() { TypeInfoResolver = JsonSerializerOptions.Default.TypeInfoResolver }; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(typeof(MyClass), "test"); @@ -121,7 +121,7 @@ public static void JsonPropertyInfoCustomConverterIsNullWhenUsingCreateJsonPrope [Fact] public static void JsonPropertyInfoCustomConverterIsNotNullForPropertyWithCustomConverter() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); Assert.Equal(1, typeInfo.Properties.Count); JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; @@ -259,7 +259,7 @@ public static void JsonPropertyInfoCustomConverterFactoryIsNotExpandedWhenSetInR [Fact] public static void JsonPropertyInfoGetIsNullAndMutableWhenUsingCreateJsonPropertyInfo() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(typeof(MyClass), "test"); Assert.Null(propertyInfo.Get); @@ -275,7 +275,7 @@ public static void JsonPropertyInfoGetIsNullAndMutableWhenUsingCreateJsonPropert [Fact] public static void JsonPropertyInfoGetIsNotNullForDefaultResolver() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; @@ -383,7 +383,7 @@ public static void JsonPropertyInfoGetIsRespected(bool useCustomConverter) [Fact] public static void JsonPropertyInfoSetIsNullAndMutableWhenUsingCreateJsonPropertyInfo() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(typeof(MyClass), "test"); Assert.Null(propertyInfo.Set); @@ -399,7 +399,7 @@ public static void JsonPropertyInfoSetIsNullAndMutableWhenUsingCreateJsonPropert [Fact] public static void JsonPropertyInfoSetIsNotNullForDefaultResolver() { - JsonSerializerOptions options = new(); + JsonSerializerOptions options = JsonSerializerOptions.Default; JsonTypeInfo typeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(TestClassWithCustomConverterOnProperty), options); Assert.Equal(1, typeInfo.Properties.Count); JsonPropertyInfo propertyInfo = typeInfo.Properties[0]; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs index a58cdea5678c8..b1e6f2bac9b79 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs @@ -37,17 +37,12 @@ public static void SetOptionsFail() TestIListNonThrowingOperationsWhenMutable(options.Converters, () => new TestConverter()); - // Verify default TypeInfoResolver throws - Action tiModifier = (ti) => { }; - Assert.Throws(() => (options.TypeInfoResolver as DefaultJsonTypeInfoResolver).Modifiers.Clear()); - Assert.Throws(() => (options.TypeInfoResolver as DefaultJsonTypeInfoResolver).Modifiers.Add(tiModifier)); - Assert.Throws(() => (options.TypeInfoResolver as DefaultJsonTypeInfoResolver).Modifiers.Insert(0, tiModifier)); - // Now set DefaultTypeInfoResolver options.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); TestIListNonThrowingOperationsWhenMutable((options.TypeInfoResolver as DefaultJsonTypeInfoResolver).Modifiers, () => (ti) => { }); // Add one item for later. + Action tiModifier = (ti) => { }; TestConverter tc = new TestConverter(); options.Converters.Add(tc); (options.TypeInfoResolver as DefaultJsonTypeInfoResolver).Modifiers.Add(tiModifier); @@ -136,12 +131,10 @@ static void TestIListThrowingOperationsWhenImmutable(IList list, T firstEl } [Fact] - public static void TypeInfoResolverIsNotNullAndCorrectType() + public static void NewDefaultOptions_TypeInfoResolverIsNull() { var options = new JsonSerializerOptions(); - Assert.NotNull(options.TypeInfoResolver); - Assert.IsType(options.TypeInfoResolver); - Assert.Same(options.TypeInfoResolver, options.TypeInfoResolver); + Assert.Null(options.TypeInfoResolver); } [Fact] @@ -670,7 +663,7 @@ public static void CopyConstructor_CopiesJsonSerializerContext() // it is possible to reset the resolver newOptions.TypeInfoResolver = null; - Assert.IsType(newOptions.TypeInfoResolver); + Assert.Null(newOptions.TypeInfoResolver); } [Fact] @@ -681,9 +674,9 @@ public static void CopyConstructor_NullInput() } [Fact] - public static void JsonSerializerOptions_Default_MatchesDefaultConstructor() + public static void JsonSerializerOptions_Default_MatchesDefaultConstructorWithDefaultResolver() { - var options = new JsonSerializerOptions(); + var options = new JsonSerializerOptions { TypeInfoResolver = JsonSerializerOptions.Default.TypeInfoResolver }; JsonSerializerOptions optionsSingleton = JsonSerializerOptions.Default; VerifyOptionsEqual(options, optionsSingleton); } @@ -702,6 +695,11 @@ public static void JsonSerializerOptions_Default_IsReadOnly() Assert.Throws(() => optionsSingleton.Converters.Add(new JsonStringEnumConverter())); Assert.Throws(() => optionsSingleton.AddContext()); Assert.Throws(() => new JsonContext(optionsSingleton)); + + DefaultJsonTypeInfoResolver resolver = Assert.IsType(optionsSingleton.TypeInfoResolver); + Assert.Throws(() => resolver.Modifiers.Clear()); + Assert.Throws(() => resolver.Modifiers.Add(ti => { })); + Assert.Throws(() => resolver.Modifiers.Insert(0, ti => { })); } [Fact] @@ -928,6 +926,11 @@ public static void ConverterRead_VerifyInvalidTypeToConvertFails() public static void GetTypeInfo_MutableOptionsInstance(Type type) { var options = new JsonSerializerOptions(); + + // An unset resolver results in NotSupportedException. + Assert.Throws(() => options.GetTypeInfo(type)); + + options.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); JsonTypeInfo typeInfo = options.GetTypeInfo(type); Assert.Equal(type, typeInfo.Type); @@ -959,7 +962,7 @@ public static void GetTypeInfo_ImmutableOptionsInstance(Type type) [Fact] public static void GetTypeInfo_MutableOptions_CanModifyMetadata() { - var options = new JsonSerializerOptions(); + var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; JsonTypeInfo jti = (JsonTypeInfo)options.GetTypeInfo(typeof(TestClassForEncoding)); Assert.Equal(1, jti.Properties.Count); @@ -1062,7 +1065,7 @@ public static void GetTypeInfo_ResolverWithoutMetadata_ThrowsNotSupportedExcepti [MemberData(nameof(GetTypeInfo_ResultsAreGeneric_Values))] public static void GetTypeInfo_ResultsAreGeneric(T value, string expectedJson) { - var options = new JsonSerializerOptions(); + var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; JsonTypeInfo jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(T)); string json = JsonSerializer.Serialize(value, jsonTypeInfo); Assert.Equal(expectedJson, json); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TypeInfoResolverFunctionalTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TypeInfoResolverFunctionalTests.cs index 770de5693ff47..857a9788e5fee 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TypeInfoResolverFunctionalTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/TypeInfoResolverFunctionalTests.cs @@ -615,7 +615,7 @@ public static void CombineCustomResolverWithDefault() return ti; }); - JsonSerializerOptions options = new JsonSerializerOptions(); + JsonSerializerOptions options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; options.IncludeFields = true; options.TypeInfoResolver = JsonTypeInfoResolver.Combine(resolver, options.TypeInfoResolver);