From d5dd38886559aa5012ccd2dba15773eb1f4dab06 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 2 Jul 2026 19:11:58 +0300 Subject: [PATCH 1/4] Enable trim/AOT analyzers on System.Text.Json source generator tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../CollectionTests.AsyncEnumerable.cs | 36 ++-- .../CollectionTests.Dictionary.cs | 9 +- .../CollectionTests.KeyValuePair.cs | 3 +- .../ConstructorTests.Cache.cs | 13 +- .../ConstructorTests.ParameterMatching.cs | 2 +- .../tests/Common/ExtensionDataTests.cs | 3 +- .../JsonCreationHandlingTests.Object.cs | 12 +- .../tests/Common/JsonSchemaExporterTests.cs | 14 +- .../tests/Common/JsonSerializerWrapper.cs | 7 + .../tests/Common/JsonTestHelper.cs | 2 + .../tests/Common/MetadataTests.Options.cs | 14 +- .../tests/Common/MetadataTests.cs | 11 +- .../tests/Common/NullableAnnotationsTests.cs | 12 +- .../tests/Common/NumberHandlingTests.cs | 39 +++- .../PolymorphicTests.CustomTypeHierarchies.cs | 194 +++++++++--------- .../Common/PolymorphicTests.TypeClassifier.cs | 14 +- .../tests/Common/PolymorphicTests.cs | 25 +-- .../PropertyVisibilityTests.InitOnly.cs | 17 +- ...pertyVisibilityTests.NonPublicAccessors.cs | 7 +- .../tests/Common/PropertyVisibilityTests.cs | 17 +- .../ReferenceHandlerTests.IgnoreCycles.cs | 19 +- .../ReferenceHandlerTests.Serialize.cs | 23 ++- .../ReferenceHandlerTests.cs | 77 ++----- .../tests/Common/RequiredKeywordTests.cs | 12 +- .../tests/Common/SerializerTests.cs | 35 +++- .../StructuralJsonTypeClassifierTests.cs | 55 +++-- .../tests/Common/UnionTests.cs | 10 +- .../tests/Common/UnsupportedTypesTests.cs | 5 +- .../JsonSerializerContextTests.cs | 27 ++- .../JsonSourceGenerationOptionsTests.cs | 6 + .../RealWorldContextTests.cs | 2 +- .../JsonSerializerWrapper.SourceGen.cs | 24 +-- .../Serialization/PolymorphicTests.cs | 7 + .../Serialization/PropertyVisibilityTests.cs | 29 +-- .../SerializationLogicTests.cs | 4 +- ...n.SourceGeneration.Roslyn3.11.Tests.csproj | 3 - ...on.SourceGeneration.Roslyn4.4.Tests.csproj | 3 - ...m.Text.Json.SourceGeneration.Tests.targets | 5 + .../TestClasses.CustomConverters.cs | 55 ++--- .../JsonNode/JsonValueTests.cs | 4 +- .../Serialization/DomTests.cs | 22 +- .../System.Text.Json.Tests.csproj | 5 + 42 files changed, 485 insertions(+), 398 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs index a6896e3d30dd32..7d59d15b71930f 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -26,7 +27,7 @@ public async Task WriteRootLevelAsyncEnumerable(IEnumerable JsonSerializerOptions options = Serializer.CreateOptions(makeReadOnly: false); options.DefaultBufferSize = bufferSize; - string expectedJson = JsonSerializer.Serialize(source, options); + string expectedJson = await Serializer.SerializeWrapper(source, options); using var stream = new Utf8MemoryStream(); var asyncEnumerable = new MockedAsyncEnumerable(source, delayInterval); @@ -49,7 +50,7 @@ public async Task WriteNestedAsyncEnumerable(IEnumerable sou JsonSerializerOptions options = Serializer.CreateOptions(makeReadOnly: false); options.DefaultBufferSize = bufferSize; - string expectedJson = JsonSerializer.Serialize(new EnumerableDto { Data = source }, options); + string expectedJson = await Serializer.SerializeWrapper(new EnumerableDto { Data = source }, options); using var stream = new Utf8MemoryStream(); var asyncEnumerable = new MockedAsyncEnumerable(source, delayInterval); @@ -75,7 +76,7 @@ public async Task WriteNestedAsyncEnumerable_Nullable(IEnumerable, bool)?>((source, false), options); + string expectedJson = await Serializer.SerializeWrapper<(IEnumerable, bool)?>((source, false), options); using var stream = new Utf8MemoryStream(); var asyncEnumerable = new MockedAsyncEnumerable(source, delayInterval); @@ -94,7 +95,7 @@ public async Task WriteAsyncEnumerable_CancellationToken_IsPassedToAsyncEnumerat using var cts = new CancellationTokenSource(); IAsyncEnumerable value = CreateEnumerable(); - await JsonSerializer.SerializeAsync(utf8Stream, value, Serializer.DefaultOptions, cancellationToken: cts.Token); + await JsonSerializer.SerializeAsync(utf8Stream, value, Serializer.DefaultOptions.GetTypeInfo>(), cts.Token); Assert.Equal("[1,2]", utf8Stream.AsString()); async IAsyncEnumerable CreateEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -128,7 +129,7 @@ public async Task WriteAsyncEnumerable_LongRunningEnumeration_Cancellation( using var utf8Stream = new Utf8MemoryStream(); using var cts = new CancellationTokenSource(delay: TimeSpan.FromMilliseconds(cancellationTokenSourceDelayMilliseconds)); await Assert.ThrowsAsync(async () => - await JsonSerializer.SerializeAsync(utf8Stream, longRunningEnumerable, Serializer.DefaultOptions, cancellationToken: cts.Token)); + await JsonSerializer.SerializeAsync(utf8Stream, longRunningEnumerable, Serializer.DefaultOptions.GetTypeInfo>(), cts.Token)); Assert.Equal(1, longRunningEnumerable.TotalCreatedEnumerators); Assert.Equal(1, longRunningEnumerable.TotalDisposedEnumerators); @@ -168,7 +169,7 @@ public async Task WriteSequentialNestedAsyncEnumerables(IEnumerable { Data1 = source, Data2 = source }, options); + string expectedJson = await Serializer.SerializeWrapper(new EnumerableDtoWithTwoProperties { Data1 = source, Data2 = source }, options); using var stream = new Utf8MemoryStream(); var asyncEnumerable = new MockedAsyncEnumerable(source, delayInterval); @@ -192,7 +193,7 @@ public async Task WriteAsyncEnumerableOfAsyncEnumerables(IEnumerable(source, delayInterval); var outerAsyncEnumerable = @@ -213,16 +214,16 @@ public async Task WriteAsyncEnumerableOfAsyncEnumerables(IEnumerable asyncEnumerable = new MockedAsyncEnumerable(Enumerable.Range(1, 10)); - Assert.Throws(() => JsonSerializer.Serialize(asyncEnumerable, Serializer.DefaultOptions)); - Assert.Throws(() => JsonSerializer.Serialize(new MemoryStream(), asyncEnumerable, Serializer.DefaultOptions)); + Assert.Throws(() => JsonSerializer.Serialize(asyncEnumerable, Serializer.DefaultOptions.GetTypeInfo>())); + Assert.Throws(() => JsonSerializer.Serialize(new MemoryStream(), asyncEnumerable, Serializer.DefaultOptions.GetTypeInfo>())); } [Fact] public void WriteNestedAsyncEnumerableSync_ThrowsNotSupportedException() { IAsyncEnumerable asyncEnumerable = new MockedAsyncEnumerable(Enumerable.Range(1, 10)); - Assert.Throws(() => JsonSerializer.Serialize(new AsyncEnumerableDto { Data = asyncEnumerable }, Serializer.DefaultOptions)); - Assert.Throws(() => JsonSerializer.Serialize(new MemoryStream(), new AsyncEnumerableDto { Data = asyncEnumerable }, Serializer.DefaultOptions)); + Assert.Throws(() => JsonSerializer.Serialize(new AsyncEnumerableDto { Data = asyncEnumerable }, Serializer.DefaultOptions.GetTypeInfo>())); + Assert.Throws(() => JsonSerializer.Serialize(new MemoryStream(), new AsyncEnumerableDto { Data = asyncEnumerable }, Serializer.DefaultOptions.GetTypeInfo>())); } [Fact] @@ -331,7 +332,7 @@ public async Task RegressionTest_DisposingEnumeratorOnPendingMoveNextAsyncOperat // Regression test for https://github.com/dotnet/runtime/issues/57360 using var stream = new Utf8MemoryStream(); using var cts = new CancellationTokenSource(millisecondsDelay: 1000); - await Assert.ThrowsAsync(async () => await JsonSerializer.SerializeAsync(stream, GetNumbersAsync(), Serializer.DefaultOptions, cancellationToken: cts.Token)); + await Assert.ThrowsAsync(async () => await JsonSerializer.SerializeAsync(stream, GetNumbersAsync(), Serializer.DefaultOptions.GetTypeInfo>(), cts.Token)); static async IAsyncEnumerable GetNumbersAsync() { @@ -417,13 +418,6 @@ public async Task WriteNestedAsyncEnumerableInsideAsyncEnumerable_InnerEnumerato return; } - // This test requires reflection to serialize custom IAsyncEnumerable types; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return; - } - var events = new List(); var inner1 = new DisposalTrackingAsyncEnumerable( new[] { 1, 2 }, "Inner1", events, asyncDisposal); @@ -434,7 +428,7 @@ public async Task WriteNestedAsyncEnumerableInsideAsyncEnumerable_InnerEnumerato new IAsyncEnumerable[] { inner1, inner2 }, "Outer", events, asyncDisposal); using var stream = new Utf8MemoryStream(); - await JsonSerializer.SerializeAsync>>(stream, outer); + await JsonSerializer.SerializeAsync(stream, (IAsyncEnumerable>)outer, Serializer.DefaultOptions.GetTypeInfo>>()); JsonTestHelper.AssertJsonEqual("[[1,2],[3,4]]", stream.AsString()); diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs index d98d579ecae79d..b6b4559c9c86f6 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -731,6 +732,8 @@ public override void Write(Utf8JsonWriter writer, MyClass value, JsonSerializerO // levels deep, along with matching JSON, encompassing the various planes of // dictionaries that can be combined: generic, non-generic, BCL, user-derived, // immutable, mutable, readonly, concurrent, specialized. + [RequiresUnreferencedCode("Uses Type.MakeGenericType to construct nested dictionary types.")] + [RequiresDynamicCode("Uses Type.MakeGenericType to construct nested dictionary types.")] private static IEnumerable<(Type, string)> NestedDictionaryTypeData() { string testJson = """{"Key":1}"""; @@ -793,6 +796,8 @@ public override void Write(Utf8JsonWriter writer, MyClass value, JsonSerializerO #endif [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/66220", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [RequiresUnreferencedCode("Uses Type.MakeGenericType to construct nested dictionary types.")] + [RequiresDynamicCode("Uses Type.MakeGenericType to construct nested dictionary types.")] public async Task NestedDictionariesRoundtrip() { JsonSerializerOptions options = new JsonSerializerOptions(); @@ -1300,11 +1305,11 @@ public async Task IgnoreDictionaryPropertyWithDifferentOrdering() await VerifyIgnore(false, true, false, addMissing: true); } - private async Task VerifyIgnore(bool skip1, bool skip2, bool skip3, bool addMissing = false) + private async Task VerifyIgnore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool skip1, bool skip2, bool skip3, bool addMissing = false) { static IDictionary GetProperty(T objectToVerify, string propertyName) { - return (IDictionary)objectToVerify.GetType().GetProperty(propertyName).GetValue(objectToVerify); + return (IDictionary)typeof(T).GetProperty(propertyName).GetValue(objectToVerify); } void Verify(T objectToVerify) diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs index 4f3fdc487b060c..e7b58778678f51 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.Encodings.Web; using System.Threading.Tasks; using Xunit; @@ -396,7 +397,7 @@ private class TrailingAngleBracketPolicy : JsonNamingPolicy [Theory] [InlineData(typeof(KeyNameNullPolicy), "Key")] [InlineData(typeof(ValueNameNullPolicy), "Value")] - public async Task InvalidPropertyNameFail(Type policyType, string offendingProperty) + public async Task InvalidPropertyNameFail([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type policyType, string offendingProperty) { var options = new JsonSerializerOptions { diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs index cf05d3a88d4d1f..6cb3ba3e1616d9 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading.Tasks; using Xunit; @@ -34,20 +35,20 @@ async Task DeserializeObjectAsync(string json, Type type, JsonSerializerOptions ((ITestClassWithParameterizedCtor)obj).Verify(); } - async Task DeserializeObjectMinimalAsync(Type type, JsonSerializerOptions options) + async Task DeserializeObjectMinimalAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, JsonSerializerOptions options) { string json = (string)type.GetProperty("s_json_minimal").GetValue(null); var obj = await Serializer.DeserializeWrapper(json, type, options); ((ITestClassWithParameterizedCtor)obj).VerifyMinimal(); }; - async Task DeserializeObjectFlippedAsync(Type type, JsonSerializerOptions options) + async Task DeserializeObjectFlippedAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, JsonSerializerOptions options) { string json = (string)type.GetProperty("s_json_flipped").GetValue(null); await DeserializeObjectAsync(json, type, options); }; - async Task DeserializeObjectNormalAsync(Type type, JsonSerializerOptions options) + async Task DeserializeObjectNormalAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, JsonSerializerOptions options) { string json = (string)type.GetProperty("s_json").GetValue(null); await DeserializeObjectAsync(json, type, options); @@ -59,7 +60,7 @@ async Task SerializeObject(Type type, JsonSerializerOptions options) await Serializer.SerializeWrapper(obj, options); }; - async Task RunTestAsync(Type type) + async Task RunTestAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Use local options to avoid obtaining already cached metadata from the default options. JsonSerializerOptions options = Serializer.CreateOptions(); @@ -129,7 +130,7 @@ public async Task PropertyCacheWithMinInputsLast() [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", ~RuntimeConfiguration.Release)] public async Task MultipleTypes() { - async Task Serialize(object[] args) + async Task Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(object[] args) { Type type = typeof(T); @@ -145,7 +146,7 @@ async Task DeserializeAsync(string json) obj.Verify(); }; - async Task RunTestAsync(T testObj, object[] args) + async Task RunTestAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(T testObj, object[] args) { // Get the test json with the default options to avoid cache pollution of DeserializeAsync() below. ((ITestClass)testObj).Initialize(); diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs index 879894d680761f..217a5d7224ed0e 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs @@ -731,7 +731,7 @@ public async Task ConstructorHandlingHonorsCustomConverters() [Fact] public async Task CanDeserialize_ObjectWith_Ctor_With_64_Params() { - async Task RunTestAsync() + async Task RunTestAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() { StringBuilder sb = new StringBuilder(); sb.Append("{"); diff --git a/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs index dbf0bae31d62d8..dc823633137b01 100644 --- a/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json.Nodes; @@ -1353,7 +1354,7 @@ public class ClassWithExtensionPropertyCustomIImmutableJsonElement [InlineData(typeof(ClassWithExtensionPropertyNoGenericParameters))] [InlineData(typeof(ClassWithExtensionPropertyOneGenericParameter))] [InlineData(typeof(ClassWithExtensionPropertyThreeGenericParameters))] - public async Task DeserializeIntoGenericDictionaryParameterCount(Type type) + public async Task DeserializeIntoGenericDictionaryParameterCount([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { object obj = await Serializer.DeserializeWrapper("{\"hello\":\"world\"}", type); diff --git a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs index bd91642bbe7f70..c74c9b1902ca39 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Text.Json.Serialization.Tests; @@ -825,10 +827,11 @@ public class ClassWithRecursiveRequiredProperty public ClassWithRecursiveRequiredProperty? Next { get; set; } } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] [InlineData(typeof(SimpleClass))] [InlineData(typeof(SimpleClassWithSmallParametrizedCtor))] [InlineData(typeof(SimpleClassWithLargeParametrizedCtor))] + [RequiresDynamicCode("Uses Type.MakeGenericType to construct the generic type under test.")] public async Task CreationHandlingSetWithAttribute_CanPopulateOrSerialize_Callbacks(Type type) { Type finalType = typeof(ClassWithWritableProperty<>).MakeGenericType(type); @@ -879,7 +882,10 @@ async Task DeserializeAndEnsureCallbacksCalled(string json, int expectedTimesCal [InlineData(typeof(ClassImplementingInterfaceWithPopulateOnTypeWithInterfaceProperty), null, false)] [InlineData(typeof(IInterfaceWithInterfaceProperty), typeof(ClassImplementingInterfaceWithInterfaceProperty), false)] [InlineData(typeof(ClassImplementingInterfaceWithInterfaceProperty), null, true)] - public async Task CreationHandlingSetWithAttributeOnType_Interfaces(Type typeToDeserialize, Type? typeToCreate, bool shouldPopulate) + public async Task CreationHandlingSetWithAttributeOnType_Interfaces( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type typeToDeserialize, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? typeToCreate, + bool shouldPopulate) { JsonSerializerOptions options = Serializer.CreateOptions(modifier: ti => { @@ -1169,7 +1175,7 @@ public class ClassWithInvalidPropertyAnnotation [Theory] [InlineData(typeof(ClassWithParameterizedConstructorWithPopulateProperty))] [InlineData(typeof(ClassWithParameterizedConstructorWithPopulateType))] - public async Task ClassWithParameterizedCtor_UsingPopulateConfiguration_ThrowsNotSupportedException(Type type) + public async Task ClassWithParameterizedCtor_UsingPopulateConfiguration_ThrowsNotSupportedException([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) { object instance = Activator.CreateInstance(type, "Jim"); string json = """{"Username":"Jim","PhoneNumbers":["123456"]}"""; diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs index 0e6770c02ddc56..f6771e365c66ba 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -11,6 +12,7 @@ using System.Text.Json.Serialization.Tests; using System.Xml.Linq; using Json.Schema; +using Microsoft.DotNet.XUnitExtensions; using Xunit; using Xunit.Sdk; @@ -38,8 +40,10 @@ public void TestTypes_GeneratesExpectedJsonSchema(ITestData testData) AssertValidJsonSchema(testData.Type, testData.ExpectedJsonSchema, schema); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] [MemberData(nameof(GetTestDataUsingAllValues))] + [RequiresUnreferencedCode("Uses reflection-based JsonSerializer.SerializeToNode(object, Type, options).")] + [RequiresDynamicCode("Uses reflection-based JsonSerializer.SerializeToNode(object, Type, options).")] public void TestTypes_SerializedValueMatchesGeneratedSchema(ITestData testData) { JsonSerializerOptions options = testData.SerializerOptions is { } opts @@ -215,8 +219,9 @@ public void JsonSchemaExporterOptions_Default_IsSame() Assert.Same(JsonSchemaExporterOptions.Default, JsonSchemaExporterOptions.Default); } -#if !BUILDING_SOURCE_GENERATOR_TESTS - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [RequiresUnreferencedCode("Uses private reflection to access System.Text.Json converter internals.")] + [RequiresDynamicCode("Uses private reflection to access System.Text.Json converter internals.")] public void LegacySchemaExporter_CanAccessReflectedMembers() { // A number of libraries such as Microsoft.Extensions.AI and Semantic Kernel @@ -259,7 +264,6 @@ record PocoWithProperty(int Value); [JsonSerializable(typeof(PocoWithProperty))] partial class PocoWithPropertyContext : JsonSerializerContext; -#endif protected void AssertValidJsonSchema(Type type, string expectedJsonSchema, JsonNode actualJsonSchema) { @@ -326,6 +330,6 @@ private EvaluationResults EvaluateSchemaCore(JsonNode schema, JsonNode? instance }; private string FormatJson(JsonNode? node) => - JsonSerializer.Serialize(node, _indentedOptions); + JsonSerializer.Serialize(node, _indentedOptions.GetTypeInfo()); } } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs index 710304c9d73965..f453f04d807d3a 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs @@ -73,7 +73,14 @@ public abstract IAsyncEnumerable DeserializeAsyncEnumerable( public virtual JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions? options = null, bool mutable = false) { options ??= DefaultOptions; +#if BUILDING_SOURCE_GENERATOR_TESTS + // In the source generator test project the options are always backed by a + // JsonSerializerContext resolver, so there is never a missing resolver to populate. + // Use the parameterless overload which is safe for trimming and Native AOT. + options.MakeReadOnly(); +#else options.MakeReadOnly(populateMissingResolver: true); +#endif return mutable ? options.TypeInfoResolver.GetTypeInfo(type, options) : options.GetTypeInfo(type); } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs index 7fbdfef6a7a14c..95535fa97de428 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text.Json.Serialization; @@ -157,6 +158,7 @@ static string BuildJsonPath(Stack path) } } + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void AssertOptionsEqual(JsonSerializerOptions expected, JsonSerializerOptions actual) { foreach (PropertyInfo property in typeof(JsonSerializerOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance)) diff --git a/src/libraries/System.Text.Json/tests/Common/MetadataTests.Options.cs b/src/libraries/System.Text.Json/tests/Common/MetadataTests.Options.cs index 8341415bd84815..9b147d0d40914a 100644 --- a/src/libraries/System.Text.Json/tests/Common/MetadataTests.Options.cs +++ b/src/libraries/System.Text.Json/tests/Common/MetadataTests.Options.cs @@ -89,20 +89,21 @@ public void OptionsImmutableAfterBinding() [Fact] public void PassingImmutableOptionsThrowsException() { - JsonSerializerOptions defaultOptions = JsonSerializerOptions.Default; - Assert.Throws(() => new MyJsonContext(defaultOptions)); + JsonSerializerOptions options = Serializer.DefaultOptions; + Assert.True(options.IsReadOnly); + + Assert.Throws(() => new MyJsonContext(options)); } [Fact] public void PassingWrongOptionsInstanceToResolverThrowsException() { - JsonSerializerOptions defaultOptions = JsonSerializerOptions.Default; JsonSerializerOptions contextOptions = new(); IJsonTypeInfoResolver context = new EmptyContext(contextOptions); Assert.IsAssignableFrom>(context.GetTypeInfo(typeof(int), contextOptions)); Assert.IsAssignableFrom>(context.GetTypeInfo(typeof(int), null)); - Assert.Throws(() => context.GetTypeInfo(typeof(int), defaultOptions)); + Assert.Throws(() => context.GetTypeInfo(typeof(int), Serializer.DefaultOptions)); } private class MyJsonContext : JsonSerializerContext @@ -127,7 +128,10 @@ private class EmptyContext : JsonSerializerContext { public EmptyContext(JsonSerializerOptions options) : base(options) { } protected override JsonSerializerOptions? GeneratedSerializerOptions => null; - public override JsonTypeInfo? GetTypeInfo(Type type) => JsonTypeInfo.CreateJsonTypeInfo(type, Options); + public override JsonTypeInfo? GetTypeInfo(Type type) + => type == typeof(int) + ? JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.Int32Converter) + : null; } } } diff --git a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs index 4aaf962626c994..923a905d6730bb 100644 --- a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text.Json.Serialization.Metadata; @@ -37,7 +38,7 @@ public void TypeWithoutConstructor_TypeInfoReportsNullCtorProvider(Type type) [InlineData(typeof(ClassWithParameterizedCtor))] [InlineData(typeof(ClassWithMultipleConstructors))] [InlineData(typeof(DerivedClassWithShadowingProperties))] - public void TypeWithConstructor_TypeInfoReportsExpectedCtorProvider(Type typeWithCtor) + public void TypeWithConstructor_TypeInfoReportsExpectedCtorProvider([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeWithCtor) { ConstructorInfo? expectedCtor = typeWithCtor.GetConstructors(BindingFlags.Public | BindingFlags.Instance) .OrderByDescending(ctor => ctor.GetCustomAttribute() is not null) @@ -56,7 +57,7 @@ public void TypeWithConstructor_TypeInfoReportsExpectedCtorProvider(Type typeWit [InlineData(typeof(ClassWithParameterizedCtor))] [InlineData(typeof(ClassWithMultipleConstructors))] [InlineData(typeof(DerivedClassWithShadowingProperties))] - public void TypeWithConstructor_SettingCtorDelegate_ResetsCtorAttributeProvider(Type typeWithCtor) + public void TypeWithConstructor_SettingCtorDelegate_ResetsCtorAttributeProvider([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type typeWithCtor) { JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeWithCtor, mutable: true); Assert.NotNull(typeInfo.ConstructorAttributeProvider); @@ -97,6 +98,7 @@ public void JsonPropertyInfo_DeclaringType_HasExpectedValue(Type typeWithPropert [InlineData(typeof(ClassWithMultipleConstructors))] [InlineData(typeof(DerivedClassWithShadowingProperties))] [InlineData(typeof(IDerivedInterface))] + [RequiresUnreferencedCode("Uses reflection to resolve the member backing each JsonPropertyInfo.")] public void JsonPropertyInfo_AttributeProvider_HasExpectedValue(Type typeWithProperties) { JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeWithProperties); @@ -129,7 +131,7 @@ public void TypeWithoutConstructor_JsonPropertyInfo_AssociatedParameter_IsNull(T [InlineData(typeof(StructWithParameterizedCtor))] [InlineData(typeof(ClassWithMultipleConstructors))] [InlineData(typeof(DerivedClassWithShadowingProperties))] - public void TypeWithConstructor_JsonPropertyInfo_AssociatedParameter_MatchesCtorParams(Type typeWithCtor) + public void TypeWithConstructor_JsonPropertyInfo_AssociatedParameter_MatchesCtorParams([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeWithCtor) { ConstructorInfo? expectedCtor = typeWithCtor.GetConstructors(BindingFlags.Public | BindingFlags.Instance) .OrderByDescending(ctor => ctor.GetCustomAttribute() is not null) @@ -243,7 +245,7 @@ public void TypeWithInitOnlyAndRequiredMembers_OnlyRequiredHasAssociatedParamete [InlineData(typeof(ClassWithInitOnlyProperty))] [InlineData(typeof(ClassWithMultipleConstructors))] [InlineData(typeof(DerivedClassWithShadowingProperties))] - public void TypeWithConstructor_SettingCtorDelegate_ResetsAssociatedParameters(Type typeWithCtor) + public void TypeWithConstructor_SettingCtorDelegate_ResetsAssociatedParameters([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type typeWithCtor) { JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeWithCtor, mutable: true); Assert.NotEmpty(typeInfo.Properties); @@ -424,6 +426,7 @@ public void CollectionWithRefStructElement_Serialization() return defaultValue; } + [RequiresUnreferencedCode("Uses Type.GetMember/Type.GetInterfaces reflection to resolve the member backing a JsonPropertyInfo.")] private static MemberInfo? ResolveMember(Type type, string name) { MemberInfo? result = type.GetMember(name, BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(); diff --git a/src/libraries/System.Text.Json/tests/Common/NullableAnnotationsTests.cs b/src/libraries/System.Text.Json/tests/Common/NullableAnnotationsTests.cs index 5ce2030301c0f9..f0e120986ae179 100644 --- a/src/libraries/System.Text.Json/tests/Common/NullableAnnotationsTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/NullableAnnotationsTests.cs @@ -22,7 +22,7 @@ protected NullableAnnotationsTests(JsonSerializerWrapper serializerUnderTest) [Theory] [MemberData(nameof(GetTypesWithNonNullablePropertyGetter))] - public async Task WriteNullFromNotNullablePropertyGetter_EnforcedNullability_ThrowsJsonException(Type type, string propertyName) + public async Task WriteNullFromNotNullablePropertyGetter_EnforcedNullability_ThrowsJsonException([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string propertyName) { object value = Activator.CreateInstance(type)!; @@ -34,7 +34,7 @@ public async Task WriteNullFromNotNullablePropertyGetter_EnforcedNullability_Thr [Theory] [MemberData(nameof(GetTypesWithNonNullablePropertyGetter))] - public async Task WriteNullFromNotNullablePropertyGetter_IgnoredNullability_Succeeds(Type type, string _) + public async Task WriteNullFromNotNullablePropertyGetter_IgnoredNullability_Succeeds([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string _) { object value = Activator.CreateInstance(type)!; string json = await Serializer.SerializeWrapper(value, type, s_optionsWithIgnoredNullability); @@ -43,7 +43,7 @@ public async Task WriteNullFromNotNullablePropertyGetter_IgnoredNullability_Succ [Theory] [MemberData(nameof(GetTypesWithNonNullablePropertyGetter))] - public async Task WriteNullFromNotNullablePropertyGetter_EnforcedNullability_DisabledFlag_Succeeds(Type type, string propertyName) + public async Task WriteNullFromNotNullablePropertyGetter_EnforcedNullability_DisabledFlag_Succeeds([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string propertyName) { object value = Activator.CreateInstance(type)!; JsonTypeInfo typeInfo = Serializer.GetTypeInfo(type, s_optionsWithEnforcedNullability, mutable: true); @@ -80,7 +80,7 @@ public static IEnumerable GetTypesWithNonNullablePropertyGetter() [Theory] [MemberData(nameof(GetTypesWithNullablePropertyGetter))] - public async Task WriteNullFromNullablePropertyGetter_EnforcedNullability_Succeeds(Type type, string _) + public async Task WriteNullFromNullablePropertyGetter_EnforcedNullability_Succeeds([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string _) { object value = Activator.CreateInstance(type)!; string json = await Serializer.SerializeWrapper(value, type, s_optionsWithEnforcedNullability); @@ -89,7 +89,7 @@ public async Task WriteNullFromNullablePropertyGetter_EnforcedNullability_Succee [Theory] [MemberData(nameof(GetTypesWithNullablePropertyGetter))] - public async Task WriteNullFromNullablePropertyGetter_IgnoredNullability_Succeeds(Type type, string _) + public async Task WriteNullFromNullablePropertyGetter_IgnoredNullability_Succeeds([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string _) { object value = Activator.CreateInstance(type)!; string json = await Serializer.SerializeWrapper(value, type, s_optionsWithIgnoredNullability); @@ -98,7 +98,7 @@ public async Task WriteNullFromNullablePropertyGetter_IgnoredNullability_Succeed [Theory] [MemberData(nameof(GetTypesWithNullablePropertyGetter))] - public async Task WriteNullFromNullablePropertyGetter_EnforcedNullability_EnabledFlag_ThrowsJsonException(Type type, string propertyName) + public async Task WriteNullFromNullablePropertyGetter_EnforcedNullability_EnabledFlag_ThrowsJsonException([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string propertyName) { object value = Activator.CreateInstance(type)!; JsonTypeInfo typeInfo = Serializer.GetTypeInfo(type, s_optionsWithEnforcedNullability, mutable: true); diff --git a/src/libraries/System.Text.Json/tests/Common/NumberHandlingTests.cs b/src/libraries/System.Text.Json/tests/Common/NumberHandlingTests.cs index e42b9c48323ae9..3858cb0c15b657 100644 --- a/src/libraries/System.Text.Json/tests/Common/NumberHandlingTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/NumberHandlingTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -374,6 +375,8 @@ public class Class_With_ListsOfBoxedNonNumbers } [Fact] + [RequiresUnreferencedCode("Uses Activator.CreateInstance to construct non-generic collection types.")] + [RequiresDynamicCode("Uses Activator.CreateInstance to construct non-generic collection types.")] public async Task Number_AsCollectionElement_RoundTrip() { await RunAsCollectionElementTest(JsonNumberTestData.Bytes); @@ -419,6 +422,8 @@ public async Task Number_AsCollectionElement_RoundTrip() } } + [RequiresUnreferencedCode("Uses Activator.CreateInstance to construct non-generic collection types.")] + [RequiresDynamicCode("Uses Activator.CreateInstance to construct non-generic collection types.")] private async Task RunAsCollectionElementTest(List numbers) { StringBuilder jsonBuilder_NumbersAsNumbers = new StringBuilder(); @@ -548,6 +553,8 @@ private void AssertIEnumerableEqual(IEnumerable list1, IEnumerable list Assert.False(enumerator2.MoveNext()); } + [RequiresUnreferencedCode("Uses Activator.CreateInstance to construct non-generic collection types.")] + [RequiresDynamicCode("Uses Activator.CreateInstance to construct non-generic collection types.")] private async Task RunAllCollectionsRoundTripTest(string json) { foreach (Type type in CollectionTestTypes.DeserializableGenericEnumerableTypes()) @@ -585,10 +592,13 @@ private async Task RunAllCollectionsRoundTripTest(string json) serialized = await Serializer.SerializeWrapper(list, s_optionReadAndWriteFromStr); Assert.Equal(json, serialized); - // Serialize instance which is a collection of numbers (not JsonElements). - obj = Activator.CreateInstance(type, new[] { list }); - serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr); - Assert.Equal(json, serialized); + if (PlatformDetection.IsNotNativeAot) + { + // Serialize instance which is a collection of numbers (not JsonElements). + obj = Activator.CreateInstance(type, new[] { list }); + serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr); + Assert.Equal(json, serialized); + } } } @@ -636,6 +646,8 @@ private void AssertDictionaryElements_StringValues(string serialized) [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/39674", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))] [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", ~RuntimeConfiguration.Release)] + [RequiresUnreferencedCode("Uses Activator.CreateInstance to construct non-generic dictionary types.")] + [RequiresDynamicCode("Uses Activator.CreateInstance to construct non-generic dictionary types.")] public async Task DictionariesRoundTrip() { await RunAllDictionariessRoundTripTest(JsonNumberTestData.ULongs); @@ -643,6 +655,8 @@ public async Task DictionariesRoundTrip() await RunAllDictionariessRoundTripTest(JsonNumberTestData.Doubles); } + [RequiresUnreferencedCode("Uses Activator.CreateInstance to construct non-generic dictionary types.")] + [RequiresDynamicCode("Uses Activator.CreateInstance to construct non-generic dictionary types.")] private async Task RunAllDictionariessRoundTripTest(List numbers) { StringBuilder jsonBuilder_NumbersAsStrings = new StringBuilder(); @@ -669,14 +683,17 @@ private async Task RunAllDictionariessRoundTripTest(List numbers) JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr)); } - foreach (Type type in CollectionTestTypes.DeserializableNonGenericDictionaryTypes()) + if (PlatformDetection.IsNotNativeAot) { - Dictionary dict = await Serializer.DeserializeWrapper>(jsonNumbersAsStrings, s_optionReadAndWriteFromStr); + foreach (Type type in CollectionTestTypes.DeserializableNonGenericDictionaryTypes()) + { + Dictionary dict = await Serializer.DeserializeWrapper>(jsonNumbersAsStrings, s_optionReadAndWriteFromStr); - // Serialize instance which is a dictionary of numbers (not JsonElements). - object obj = Activator.CreateInstance(type, new[] { dict }); - string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr); - JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, serialized); + // Serialize instance which is a dictionary of numbers (not JsonElements). + object obj = Activator.CreateInstance(type, new[] { dict }); + string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr); + JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, serialized); + } } } @@ -2022,6 +2039,7 @@ await Assert.ThrowsAsync( s_optionReadFromStr)); } +#if !BUILDING_SOURCE_GENERATOR_TESTS [Fact] public void GetConverter_Int32_RespectsNumberHandling_AllowReadingFromString() { @@ -2345,5 +2363,6 @@ public void GetConverter_Single_RespectsNumberHandling_AllowNamedFloatingPointLi reader.Read(); reader.Read(); reader.Read(); Assert.True(float.IsNaN(converter.Read(ref reader, typeof(float), options))); } +#endif } } diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs index aede0175f01406..b55213d9655669 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs @@ -34,7 +34,7 @@ public Task PolymorphicClass_TestData_Deserialization(PolymorphicClass.TestData testData.ExpectedJson, testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicClass_TestData_Deserialization() => PolymorphicClass.GetSerializeTestData().Where(entry => entry.ExpectedJson != null).Select(entry => new object[] { entry }); @@ -58,7 +58,7 @@ public async Task PolymorphicClass_TestDataArray_Deserialization() .Where(entry => entry.ExpectedRoundtripValue is not null) .Select(entry => (entry.ExpectedJson, entry.ExpectedRoundtripValue)); - await TestMultiContextDeserialization(inputs, equalityComparer: PolymorphicEqualityComparer.Instance); + await TestMultiContextDeserialization(inputs, equalityComparer: CreateJsonEqualityComparer()); } [Theory] @@ -89,7 +89,7 @@ public Task PolymorphicClass_TestData_AllowOutOfOrderMetadata_Deserialization(Po testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, options: s_optionsWithAllowOutOfOrderMetadata, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); [Theory] [InlineData("""{"Number":42, "$type":"derivedClass1", "String": "str"}""", typeof(PolymorphicClass.DerivedClass1_TypeDiscriminator))] @@ -149,7 +149,7 @@ public Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Deseriali testData.ExpectedJson, testData.ExpectedRoundtripValue, testData.ExpectedSerializationException, - equalityComparer: PolymorphicEqualityComparer.Instance, + equalityComparer: CreateJsonEqualityComparer(), options: PolymorphicClass.CustomConfigWithBaseTypeFallback); public static IEnumerable Get_PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Deserialization() @@ -178,7 +178,7 @@ public async Task PolymorphicClass_CustomConfigWithBaseTypeFallbacks_TestDataArr await TestMultiContextDeserialization( inputs, - equalityComparer: PolymorphicEqualityComparer.Instance, + equalityComparer: CreateJsonEqualityComparer(), options: PolymorphicClass.CustomConfigWithBaseTypeFallback); } @@ -209,12 +209,21 @@ public async Task PolymorphicClass_CustomConfigWithBaseTypeFallback_InvalidTypeD [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization(PolymorphicClass.TestData testData) - => TestMultiContextSerialization( + { + // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return Task.CompletedTask; + } + + return TestMultiContextSerialization( testData.Value, testData.ExpectedJson, testData.ExpectedSerializationException, options: PolymorphicClass.CustomConfigWithNearestAncestorFallback, contexts: ~SerializedValueContext.BoxedValue); + } public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback().Select(entry => new object[] { entry }); @@ -222,12 +231,21 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearest [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization(PolymorphicClass.TestData testData) - => TestMultiContextDeserialization( + { + // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return Task.CompletedTask; + } + + return TestMultiContextDeserialization( testData.ExpectedJson, testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, - equalityComparer: PolymorphicEqualityComparer.Instance, + equalityComparer: CreateJsonEqualityComparer(), options: PolymorphicClass.CustomConfigWithNearestAncestorFallback); + } public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() @@ -237,6 +255,13 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearest [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Serialization() { + // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return; + } + IEnumerable<(PolymorphicClass Value, string ExpectedJson)> inputs = PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() .Where(entry => entry.ExpectedSerializationException is null) @@ -248,6 +273,13 @@ public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestD [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Deserialization() { + // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return; + } + IEnumerable<(string ExpectedJson, PolymorphicClass ExpectedRoundtripValue)> inputs = PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() .Where(entry => entry.ExpectedRoundtripValue is not null) @@ -255,7 +287,7 @@ public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestD await TestMultiContextDeserialization( inputs, - equalityComparer: PolymorphicEqualityComparer.Instance, + equalityComparer: CreateJsonEqualityComparer(), options: PolymorphicClass.CustomConfigWithNearestAncestorFallback); } @@ -302,22 +334,13 @@ public async Task PolymorphicClass_ConfigWithAbstractClass_ShouldThrowNotSupport [Fact] public async Task PolymorphicClass_ClearPolymorphismOptions_DoesNotUsePolymorphism() { - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(static jsonTypeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver() + if (jsonTypeInfo.Type == typeof(PolymorphicClass)) { - Modifiers = - { - static jsonTypeInfo => - { - if (jsonTypeInfo.Type == typeof(PolymorphicClass)) - { - jsonTypeInfo.PolymorphismOptions = null; - } - } - } + jsonTypeInfo.PolymorphismOptions = null; } - }; + }); PolymorphicClass value = new PolymorphicClass.DerivedAbstractClass.DerivedClass { Number = 42, Boolean = true }; string json = await Serializer.SerializeWrapper(value, options); @@ -961,13 +984,13 @@ public async Task PolymorphicClass_WithDerivedPolymorphicClass_Deserialization_S await TestMultiContextDeserialization( json, expectedValueUsingBaseContract, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); var expectedValueUsingDerivedContract = new PolymorphicClass_WithDerivedPolymorphicClass.DerivedClass.DerivedClass2(); await TestMultiContextDeserialization( json, expectedValueUsingDerivedContract, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); } [JsonDerivedType(typeof(DerivedClass), "derivedClass")] @@ -1149,7 +1172,7 @@ public Task PolymorphicClassWithConstructor_TestData_Deserialization(Polymorphic => TestMultiContextDeserialization( testData.ExpectedJson, testData.ExpectedRoundtripValue, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicClassWithConstructor_TestData_Deserialization() => PolymorphicClassWithConstructor.GetSerializeTestData() @@ -1171,7 +1194,7 @@ public async Task PolymorphicClassWithConstructor_TestDataArray_Deserialization( IEnumerable<(string ExpectedJson, PolymorphicClassWithConstructor ExpectedRoundtripValue)> inputs = PolymorphicClassWithConstructor.GetSerializeTestData().Select(entry => (entry.ExpectedJson, entry.ExpectedRoundtripValue)); - await TestMultiContextDeserialization(inputs, equalityComparer: PolymorphicEqualityComparer.Instance); + await TestMultiContextDeserialization(inputs, equalityComparer: CreateJsonEqualityComparer()); } [JsonPolymorphic] @@ -1300,7 +1323,7 @@ public Task PolymorphicInterface_TestData_Deserialization(PolymorphicInterface.T testData.ExpectedJson, testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicInterface_TestData_Deserialization() => PolymorphicInterface.Helpers.GetSerializeTestData() @@ -1326,7 +1349,7 @@ public async Task PolymorphicInterface_TestDataArray_Deserialization() .Where(entry => entry.ExpectedRoundtripValue is not null) .Select(entry => (entry.ExpectedJson, entry.ExpectedRoundtripValue)); - await TestMultiContextDeserialization(inputs, equalityComparer: PolymorphicEqualityComparer.Instance); + await TestMultiContextDeserialization(inputs, equalityComparer: CreateJsonEqualityComparer()); } [Theory] @@ -1369,7 +1392,7 @@ public Task PolymorphicInterface_CustomConfigWithNearestAncestorFallback_TestDat testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, options: PolymorphicInterface.Helpers.CustomConfigWithNearestAncestorFallback, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicInterface_CustomConfigWithNearestAncestorFallback_TestData_Deserialization() => PolymorphicInterface.Helpers.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() @@ -1398,7 +1421,7 @@ public async Task PolymorphicInterface_CustomConfigWithNearestAncestorFallback_T await TestMultiContextDeserialization( inputs, options: PolymorphicInterface.Helpers.CustomConfigWithNearestAncestorFallback, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); } // -- @@ -1615,7 +1638,7 @@ public Task PolymorphicList_TestData_Deserialization(PolymorphicList.TestData te => TestMultiContextDeserialization( testData.ExpectedJson, testData.ExpectedRoundtripValue, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicList_TestData_Deserialization() => PolymorphicList.GetSerializeTestData().Select(entry => new object[] { entry }); @@ -1635,7 +1658,7 @@ public async Task PolymorphicList_TestDataArray_Deserialization() IEnumerable<(string ExpectedJson, PolymorphicList ExpectedRoundtripValue)> inputs = PolymorphicList.GetSerializeTestData().Select(entry => (entry.ExpectedJson, entry.ExpectedRoundtripValue)); - await TestMultiContextDeserialization(inputs, equalityComparer: PolymorphicEqualityComparer.Instance); + await TestMultiContextDeserialization(inputs, equalityComparer: CreateJsonEqualityComparer()); } [Fact] @@ -1865,7 +1888,7 @@ public Task PolymorphicDictionary_TestData_Deserialization(PolymorphicDictionary => TestMultiContextDeserialization( testData.ExpectedJson, testData.ExpectedRoundtripValue, - equalityComparer: PolymorphicEqualityComparer.Instance); + equalityComparer: CreateJsonEqualityComparer()); public static IEnumerable Get_PolymorphicDictionary_TestData_Deserialization() => PolymorphicDictionary.GetSerializeTestData().Select(entry => new object[] { entry }); @@ -1885,7 +1908,7 @@ public async Task PolymorphicDictionary_TestDataArray_Deserialization() IEnumerable<(string ExpectedJson, PolymorphicDictionary ExpectedRoundtripValue)> inputs = PolymorphicDictionary.GetSerializeTestData().Select(entry => (entry.ExpectedJson, entry.ExpectedRoundtripValue)); - await TestMultiContextDeserialization(inputs, equalityComparer: PolymorphicEqualityComparer.Instance); + await TestMultiContextDeserialization(inputs, equalityComparer: CreateJsonEqualityComparer()); } [Fact] @@ -1947,6 +1970,13 @@ public record TestData(PolymorphicDictionary Value, string ExpectedJson, Polymor [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicDictionaryInterface_Serialization() { + // Polymorphic dictionary-interface resolution uses the reflection-based metadata fallback; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return; + } + var values = new IEnumerable>[] { new List> { new KeyValuePair(0, 0) }, @@ -1971,6 +2001,13 @@ public async Task PolymorphicDictionaryInterface_Serialization() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicDictionaryInterface_Deserialization() { + // Polymorphic dictionary-interface resolution uses the reflection-based metadata fallback; + // skip in source-gen contexts where reflection is disabled. + if (!JsonSerializer.IsReflectionEnabledByDefault) + { + return; + } + string json = """ [ [ { "Key":0, "Value":0 } ], @@ -2130,7 +2167,7 @@ public async Task ReferencePreservation_SingleValue_Deserialization(PolymorphicC { string json = jsonTemplate("1"); // root values have reference id "1" PolymorphicClass actualValue = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefs); - Assert.Equal(expectedValue, actualValue, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, actualValue, CreateJsonEqualityComparer()); } [Theory] @@ -2163,7 +2200,7 @@ public async Task ReferencePreservation_RepeatingValue_Deserialization(Polymorph var result = await Serializer.DeserializeWrapper>(json, s_jsonSerializerOptionsPreserveRefs); Assert.Equal(2, result.Count); - Assert.Equal(expectedValue, result[0], PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, result[0], CreateJsonEqualityComparer()); Assert.Same(result[0], result[1]); } @@ -2192,7 +2229,7 @@ public async Task ReferencePreservation_MultipleRepeatingValues_Deserialization( string json = "[" + string.Join(", ", idValues.Concat(refValues)) + "]"; PolymorphicClass[] result = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefs); - Assert.Equal(expectedValues, result, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValues, result, CreateJsonEqualityComparer()); } public static IEnumerable<(PolymorphicClass Value, Func JsonTemplate)> Get_ReferencePreservation_TestData() @@ -2232,7 +2269,7 @@ public async Task ReferencePreservation_CustomTypeDiscriminator_SingleValue_Dese { string json = jsonTemplate("1"); // root values have reference id "1" PolymorphicClassWithCustomTypeDiscriminator actualValue = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefs); - Assert.Equal(expectedValue, actualValue, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, actualValue, CreateJsonEqualityComparer()); } [Theory] @@ -2265,7 +2302,7 @@ public async Task ReferencePreservation_CustomTypeDiscriminator_RepeatingValue_D var result = await Serializer.DeserializeWrapper>(json, s_jsonSerializerOptionsPreserveRefs); Assert.Equal(2, result.Count); - Assert.Equal(expectedValue, result[0], PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, result[0], CreateJsonEqualityComparer()); Assert.Same(result[0], result[1]); } @@ -2294,7 +2331,7 @@ public async Task ReferencePreservation_CustomTypeDiscriminator_MultipleRepeatin string json = "[" + string.Join(", ", idValues.Concat(refValues)) + "]"; PolymorphicClassWithCustomTypeDiscriminator[] result = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefs); - Assert.Equal(expectedValues, result, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValues, result, CreateJsonEqualityComparer()); } [Theory] @@ -2303,7 +2340,7 @@ public async Task ReferencePreservation_AllowOutOfOrderMetadata_SingleValue_Dese { string json = jsonTemplate("1"); // root values have reference id "1" PolymorphicClass actualValue = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefsAndAllowReadAhead); - Assert.Equal(expectedValue, actualValue, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, actualValue, CreateJsonEqualityComparer()); } [Theory] @@ -2320,7 +2357,7 @@ public async Task ReferencePreservation_AllowOutOfOrderMetadata_RepeatingValue_D var result = await Serializer.DeserializeWrapper>(json, s_jsonSerializerOptionsPreserveRefsAndAllowReadAhead); Assert.Equal(2, result.Count); - Assert.Equal(expectedValue, result[0], PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValue, result[0], CreateJsonEqualityComparer()); Assert.Same(result[0], result[1]); } @@ -2335,7 +2372,7 @@ public async Task ReferencePreservation_AllowOutOfOrderMetadata_MultipleRepeatin string json = "[" + string.Join(", ", idValues.Concat(refValues)) + "]"; PolymorphicClass[] result = await Serializer.DeserializeWrapper(json, s_jsonSerializerOptionsPreserveRefsAndAllowReadAhead); - Assert.Equal(expectedValues, result, PolymorphicEqualityComparer.Instance); + Assert.Equal(expectedValues, result, CreateJsonEqualityComparer()); } [Theory] @@ -2947,7 +2984,14 @@ public class OpenGenericDerived_PartiallyConcrete : OpenGenericBase_Partially public T? Extra { get; set; } } - [Fact] + // Validates the runtime programmatic API by adding polymorphism to OpenGenericBase_Programmatic + // via a resolver modifier. The type is deliberately not registered with a JsonSerializerContext + // (no [JsonDerivedType]), so it relies on the reflection-based DefaultJsonTypeInfoResolver and only + // runs where runtime code generation is available; OpenGenericDerivedType_MixedWithRegularDerivedType_Works + // covers the attribute-based equivalent that also runs under source-gen. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + [RequiresUnreferencedCode("Uses DefaultJsonTypeInfoResolver and reflection-based serialization.")] + [RequiresDynamicCode("Uses DefaultJsonTypeInfoResolver and reflection-based serialization.")] public async Task OpenGenericDerivedType_ProgrammaticApi_Works() { var options = new JsonSerializerOptions @@ -3471,22 +3515,13 @@ public async Task Variance_CovariantInterface_VarianceOnlyAssignment_FallBackToB // Same as above with explicit FallBackToBaseType. The resolver falls through to the // base contract (no discriminator emitted) because the runtime type is not registered. // This matches the previously-described "serializes as base contract" behavior. - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(IVarCovBase) && typeInfo.PolymorphismOptions is { } pOpts) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(IVarCovBase) && typeInfo.PolymorphismOptions is { } pOpts) - { - pOpts.UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType; - } - } - } + pOpts.UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType; } - }; + }); IVarCovBase value = new VarCovImpl { Value = new VarDog { Name = "Rex", Breed = "Labrador" } }; string json = await Serializer.SerializeWrapper(value, options); @@ -3798,51 +3833,14 @@ public class Derived : PolymorphicClassWithEscapedTypeDiscriminator; #endregion #region Test Helpers - public class PolymorphicEqualityComparer : IEqualityComparer - where TBaseType : class - { - public static PolymorphicEqualityComparer Instance { get; } = new(); - - public bool Equals(TBaseType? left, TBaseType? right) - { - if (left is null || right is null) - { - return left is null == right is null; - } - - Type runtimeType = left.GetType(); - if (runtimeType != right.GetType()) - { - return false; - } - - EqualityComparer objComparer = EqualityComparer.Default; - - // Runtime type is enumerable; use enumerable sequence comparison - if (left is IEnumerable leftColl) - { - IEnumerable rightColl = (IEnumerable)right; - return leftColl.Cast().SequenceEqual(rightColl.Cast(), objComparer); - } - - // Runtime is regular POCO; use property structural comparison - foreach (var propInfo in runtimeType.GetProperties()) - { - if (!objComparer.Equals(propInfo.GetValue(left), propInfo.GetValue(right))) - { - return false; - } - } - - return true; - } - - public int GetHashCode(TBaseType _) => throw new NotImplementedException(); - } public class CustomPolymorphismResolver : IJsonTypeInfoResolver { + #if BUILDING_SOURCE_GENERATOR_TESTS + private readonly IJsonTypeInfoResolver _inner = System.Text.Json.SourceGeneration.Tests.PolymorphicTests_Metadata.PolymorphicTestsContext_Metadata.Default; + #else private readonly DefaultJsonTypeInfoResolver _inner = new(); + #endif private readonly List _jsonDerivedTypes = new(); public CustomPolymorphismResolver(Type baseType) diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs index 19b791b3c3665e..14d386fce2368a 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs @@ -649,7 +649,7 @@ public async Task Classifier_DiscriminatorBased_ResolvesCorrectType(string json, new JsonDerivedType(typeof(ClassifiedCat), "cat"), new JsonDerivedType(typeof(ClassifiedParrot), "parrot"),}, "kind"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); var options = CreateOptionsWithClassifier(classify); @@ -670,7 +670,7 @@ public async Task Classifier_DiscriminatorBased_AnyPropertyPosition(string json) new JsonDerivedType(typeof(ClassifiedDog), "dog"), new JsonDerivedType(typeof(ClassifiedCat), "cat"),}, "kind"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); var options = CreateOptionsWithClassifier(classify); @@ -692,7 +692,7 @@ public async Task Classifier_IntDiscriminator_ResolvesCorrectType(string json, T new JsonDerivedType(typeof(ClassifiedDog), 1), new JsonDerivedType(typeof(ClassifiedCat), 2),}, "type_id"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); var options = CreateOptionsWithClassifier(classify); @@ -996,7 +996,7 @@ public async Task Classifier_CollectionWithMixedDiscriminators() new JsonDerivedType(typeof(ClassifiedDog), "dog"), new JsonDerivedType(typeof(ClassifiedCat), "cat"),}, "kind"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); var options = CreateOptionsWithClassifier(classify); string json = """[{"kind":"dog","Name":"Rex","Breed":"Lab"},{"kind":"cat","Name":"Whiskers","Lives":9}]"""; @@ -1115,7 +1115,7 @@ public async Task Classifier_PlusAllowOutOfOrder_ClassifierWins() new JsonDerivedType(typeof(ClassifiedDog), "dog"), new JsonDerivedType(typeof(ClassifiedCat), "cat"),}, "kind"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { @@ -1239,7 +1239,7 @@ public async Task Classifier_DifferentClassifiersForDifferentBaseTypes() new JsonDerivedType(typeof(ClassifiedDog), "dog"), new JsonDerivedType(typeof(ClassifiedCat), "cat"),}, "kind"); - JsonTypeClassifier animalClassify = animalFactory.CreateJsonClassifier(animalContext, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier animalClassify = animalFactory.CreateJsonClassifier(animalContext, Serializer.DefaultOptions); JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { @@ -1462,7 +1462,7 @@ public async Task Classifier_WithReferenceHandlerPreserve_PreservesReferences() new JsonDerivedType(typeof(ClassifiedDog), "dog"), new JsonDerivedType(typeof(ClassifiedCat), "cat"),}, "kind"); - JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); + JsonTypeClassifier classify = factory.CreateJsonClassifier(context, Serializer.DefaultOptions); JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs index 06aa89d9332f95..6854c486b607f0 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs @@ -7,6 +7,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -33,8 +34,7 @@ public async Task PrimitivesAsRootObject(object? value, string expectedJson) json = await Serializer.SerializeWrapper(value, typeof(object)); Assert.Equal(expectedJson, json); - var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; - JsonTypeInfo objectTypeInfo = options.GetTypeInfo(); + JsonTypeInfo objectTypeInfo = Serializer.DefaultOptions.GetTypeInfo(); json = await Serializer.SerializeWrapper(value, objectTypeInfo); Assert.Equal(expectedJson, json); } @@ -573,23 +573,14 @@ public async Task AnonymousType() [Fact] public async Task CustomResolverWithFailingAncestorType_DoesNotSurfaceException() { - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(static typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(MyThing) || + typeInfo.Type == typeof(IList)) { - Modifiers = - { - static typeInfo => - { - if (typeInfo.Type == typeof(MyThing) || - typeInfo.Type == typeof(IList)) - { - throw new InvalidOperationException("some latent custom resolution bug"); - } - } - } + throw new InvalidOperationException("some latent custom resolution bug"); } - }; + }); object value = new MyDerivedThing { Number = 42 }; string json = await Serializer.SerializeWrapper(value, options); @@ -627,7 +618,7 @@ internal class MyThingDictionary : Dictionary { } [Theory] [InlineData(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase), typeof(PolymorphicTypeWithConflictingPropertyNameAtBase.Derived))] [InlineData(typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived), typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived.Derived))] - public async Task PolymorphicTypesWithConflictingPropertyNames_ThrowsInvalidOperationException(Type baseType, Type derivedType) + public async Task PolymorphicTypesWithConflictingPropertyNames_ThrowsInvalidOperationException(Type baseType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type derivedType) { InvalidOperationException ex; object value = Activator.CreateInstance(derivedType); diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs index d3adb66f38f2f8..b9c0b51e3b66b5 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; @@ -11,7 +12,7 @@ public abstract partial class PropertyVisibilityTests [Theory] [InlineData(typeof(ClassWithInitOnlyProperty))] [InlineData(typeof(StructWithInitOnlyProperty))] - public virtual async Task InitOnlyProperties(Type type) + public virtual async Task InitOnlyProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Init-only property included by default. object obj = await Serializer.DeserializeWrapper("""{"MyInt":1}""", type); @@ -24,7 +25,7 @@ public virtual async Task InitOnlyProperties(Type type) [Theory] [InlineData(typeof(ClassWithCustomNamedInitOnlyProperty))] [InlineData(typeof(StructWithCustomNamedInitOnlyProperty))] - public virtual async Task CustomNamedInitOnlyProperties(Type type) + public virtual async Task CustomNamedInitOnlyProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Regression test for https://github.com/dotnet/runtime/issues/82730 @@ -40,7 +41,7 @@ public virtual async Task CustomNamedInitOnlyProperties(Type type) [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter))] [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter))] [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter))] - public async Task NonPublicInitOnlySetter_Without_JsonInclude_Fails(Type type) + public async Task NonPublicInitOnlySetter_Without_JsonInclude_Fails([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Non-public init-only property setter ignored. object obj = await Serializer.DeserializeWrapper("""{"MyInt":1}""", type); @@ -54,7 +55,7 @@ public async Task NonPublicInitOnlySetter_Without_JsonInclude_Fails(Type type) [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] - public virtual async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) + public virtual async Task NonPublicInitOnlySetter_With_JsonInclude([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { // Non-public init-only property setter included with [JsonInclude]. object obj = await Serializer.DeserializeWrapper("""{"MyInt":1}""", type); @@ -67,7 +68,7 @@ public virtual async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) [Theory] [InlineData(typeof(Class_WithIgnoredInitOnlyProperty))] [InlineData(typeof(Record_WithIgnoredPropertyInCtor))] - public async Task InitOnlySetter_With_JsonIgnoreAlways(Type type) + public async Task InitOnlySetter_With_JsonIgnoreAlways([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { object obj = await Serializer.DeserializeWrapper("""{"MyInt":42}""", type); Assert.Equal(0, (int)type.GetProperty("MyInt").GetValue(obj)); @@ -196,7 +197,7 @@ public record InnerRecord(int Foo, string Bar); [Theory] [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] - public async Task InitOnlyPropertyDefaultValues_PreservedWhenMissingFromJson(Type type) + public async Task InitOnlyPropertyDefaultValues_PreservedWhenMissingFromJson([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // When no properties are present in JSON, C# default values should be preserved. object obj = await Serializer.DeserializeWrapper("{}", type); @@ -207,7 +208,7 @@ public async Task InitOnlyPropertyDefaultValues_PreservedWhenMissingFromJson(Typ [Theory] [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] - public async Task InitOnlyPropertyDefaultValues_OverriddenWhenPresentInJson(Type type) + public async Task InitOnlyPropertyDefaultValues_OverriddenWhenPresentInJson([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // When properties are present in JSON, specified values should be used. object obj = await Serializer.DeserializeWrapper("""{"Name":"Override","Number":99}""", type); @@ -218,7 +219,7 @@ public async Task InitOnlyPropertyDefaultValues_OverriddenWhenPresentInJson(Type [Theory] [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] - public async Task InitOnlyPropertyDefaultValues_PartialOverride(Type type) + public async Task InitOnlyPropertyDefaultValues_PartialOverride([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // When only some properties are present in JSON, the rest should keep defaults. object obj = await Serializer.DeserializeWrapper("""{"Number":99}""", type); diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs index 839156f7b98bbb..bc903071112aaa 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text.Json.Serialization.Metadata; @@ -169,7 +170,7 @@ private class ClassWithExtensionData_NonPublicGetter public virtual async Task HonorCustomConverter_UsingPrivateSetter() { var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonStringEnumConverter()); string json = """{"MyEnum":"AnotherValue","MyInt":2}"""; @@ -360,7 +361,7 @@ public class ClassWithMixedPropertyAccessors_PropertyAttributes [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty), false)] [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty), true)] [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty), false)] - public virtual async Task NonPublicProperty_JsonInclude_WorksAsExpected(Type type, bool isAccessibleBySourceGen) + public virtual async Task NonPublicProperty_JsonInclude_WorksAsExpected([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, bool isAccessibleBySourceGen) { if (!Serializer.IsSourceGeneratedSerializer || isAccessibleBySourceGen) { diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs index 3a5daf6e5d1182..2dbbb5325051d5 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs @@ -1,9 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -1785,7 +1786,7 @@ public async Task OverrideJsonIgnorePropertyUsingJsonPropertyNameReversed() [Theory] [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways))] [InlineData(typeof(ClassWithProperty_IgnoreConditionAlways_Ctor))] - public async Task JsonIgnoreConditionSetToAlwaysWorks(Type type) + public async Task JsonIgnoreConditionSetToAlwaysWorks([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { string json = """{"MyString":"Random","MyDateTime":"2020-03-23","MyInt":4}"""; @@ -1830,7 +1831,7 @@ public ClassWithProperty_IgnoreConditionAlways_Ctor(DateTime myDateTime, int myI [Theory] [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_ClassProperty_TestData))] - public async Task JsonIgnoreConditionWhenWritingDefault_ClassProperty(Type type, JsonSerializerOptions options) + public async Task JsonIgnoreConditionWhenWritingDefault_ClassProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, JsonSerializerOptions options) { // Property shouldn't be ignored if it isn't null. string json = """{"Int1":1,"MyString":"Random","Int2":2}"""; @@ -1916,7 +1917,7 @@ public static IEnumerable JsonIgnoreConditionWhenWritingDefault_ClassP [Theory] [MemberData(nameof(JsonIgnoreConditionWhenWritingDefault_StructProperty_TestData))] - public async Task JsonIgnoreConditionWhenWritingDefault_StructProperty(Type type, JsonSerializerOptions options) + public async Task JsonIgnoreConditionWhenWritingDefault_StructProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, JsonSerializerOptions options) { // Property shouldn't be ignored if it isn't null. string json = """{"Int1":1,"MyInt":3,"Int2":2}"""; @@ -1974,7 +1975,7 @@ public static IEnumerable JsonIgnoreConditionWhenWritingDefault_Struct [Theory] [MemberData(nameof(JsonIgnoreConditionNever_TestData))] - public async Task JsonIgnoreConditionNever(Type type) + public async Task JsonIgnoreConditionNever([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Property should always be (de)serialized, even when null. string json = """{"Int1":1,"MyString":"Random","Int2":2}"""; @@ -2017,7 +2018,7 @@ public async Task JsonIgnoreConditionNever(Type type) [Theory] [MemberData(nameof(JsonIgnoreConditionNever_TestData))] - public async Task JsonIgnoreConditionNever_IgnoreNullValues_True(Type type) + public async Task JsonIgnoreConditionNever_IgnoreNullValues_True([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // Property should always be (de)serialized. string json = """{"Int1":1,"MyString":"Random","Int2":2}"""; @@ -2903,7 +2904,7 @@ public class ClassWithThingsToIgnore_PerProperty [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] - public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(Type type) + public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { InvalidOperationException ex = await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("{}", type)); string exAsStr = ex.ToString(); @@ -2923,7 +2924,7 @@ public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(T [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] - public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + public virtual async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { InvalidOperationException ex = await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("", type)); string exAsStr = ex.ToString(); diff --git a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs index 0745c8d78e6adf..24f865a347d323 100644 --- a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs +++ b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -28,7 +29,7 @@ public async Task IgnoreCycles_OnObject() await Verify(); await Verify(); - async Task Verify() where T : class, new() + async Task Verify<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() where T : class, new() { T root = new T(); SetNextProperty(typeof(T), root, root); @@ -48,7 +49,7 @@ public async Task IgnoreCycles_OnObject_AsProperty() await Verify(); await Verify(); - async Task Verify() where T : class, new() + async Task Verify<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() where T : class, new() { var node = new T(); SetNextProperty(typeof(T), node, node); @@ -77,7 +78,7 @@ public async Task IgnoreCycles_OnBoxedValueType() await Verify(); await Verify(); - async Task Verify() where T : new() + async Task Verify<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() where T : new() { object root = new T(); SetNextProperty(typeof(T), root, root); @@ -107,7 +108,7 @@ public async Task IgnoreCycles_OnBoxedValueType_AsProperty() await Verify(); await Verify(); - async Task Verify() where T : new() + async Task Verify<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() where T : new() { object node = new T(); SetNextProperty(typeof(T), node, node); @@ -125,7 +126,7 @@ public async Task IgnoreCycles_OnBoxedValueType_AsProperty() [Theory] [InlineData(typeof(Dictionary))] [InlineData(typeof(GenericIDictionaryWrapper))] - public async Task IgnoreCycles_OnDictionary(Type typeToSerialize) + public async Task IgnoreCycles_OnDictionary([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type typeToSerialize) { var root = (IDictionary)Activator.CreateInstance(typeToSerialize); root.Add("self", root); @@ -172,7 +173,7 @@ public async Task IgnoreCycles_OnArray() [Theory] [InlineData(typeof(List))] [InlineData(typeof(GenericIListWrapper))] - public async Task IgnoreCycles_OnList(Type typeToSerialize) + public async Task IgnoreCycles_OnList([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type typeToSerialize) { var root = (IList)Activator.CreateInstance(typeToSerialize); root.Add(root); @@ -190,7 +191,7 @@ public async Task IgnoreCycles_OnRecursiveList() [Theory] [InlineData(typeof(GenericISetWrapper))] [InlineData(typeof(GenericICollectionWrapper))] - public async Task IgnoreCycles_OnCollections(Type typeToSerialize) + public async Task IgnoreCycles_OnCollections([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type typeToSerialize) { var root = (ICollection)Activator.CreateInstance(typeToSerialize); root.Add(root); @@ -531,12 +532,12 @@ static void VerifySubstringExistsNTimes(string actualString, string expectedSubs } private const string Next = nameof(Next); - private void SetNextProperty(Type type, object obj, object value) + private void SetNextProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, object obj, object value) { type.GetProperty(Next).SetValue(obj, value); } - private object GetNextProperty(Type type, object obj) + private object GetNextProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, object obj) { return type.GetProperty(Next).GetValue(obj); } diff --git a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs index 61ee63d2906f5d..8c66d4f852374e 100644 --- a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs +++ b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -14,6 +15,25 @@ public abstract partial class ReferenceHandlerTests : SerializerTests private static readonly JsonSerializerOptions s_serializerOptionsPreserve = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }; private static readonly JsonSerializerSettings s_newtonsoftSerializerSettingsPreserve = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All, ReferenceLoopHandling = ReferenceLoopHandling.Serialize }; + // Asserts that the serializer output matches the Newtonsoft.Json reference-preservation baseline. + // The Newtonsoft.Json baseline is reflection-based (its APIs are annotated RequiresUnreferencedCode + // and RequiresDynamicCode), so it is only exercised when dynamic code is supported. The runtime guard + // makes the call unreachable under Native AOT (where PlatformDetection.IsReflectionEmitSupported is + // false), while still running the baseline in the non-AOT source generator configuration. The trim/AOT + // analyzers cannot see through the guard, so the warnings are suppressed here. + [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "The Newtonsoft.Json baseline only runs when dynamic code is supported, guarded by PlatformDetection.IsReflectionEmitSupported.")] + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "The Newtonsoft.Json baseline only runs when dynamic code is supported, guarded by PlatformDetection.IsReflectionEmitSupported.")] + private static void AssertOutputEqualsNewtonsoft(object value, string actual) + { + if (PlatformDetection.IsReflectionEmitSupported) + { + string expected = JsonConvert.SerializeObject(value, s_newtonsoftSerializerSettingsPreserve); + Assert.Equal(expected, actual); + } + } + public class Employee { public string? Name { get; set; } @@ -52,10 +72,9 @@ public async Task ExtensionDataDictionaryHandlesPreserveReferences() angela.ExtensionData = extensionData; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); } #region struct tests diff --git a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.cs b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.cs index ad23a6f3af3916..f09fd6d0fb3f44 100644 --- a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.cs @@ -7,7 +7,7 @@ using System.Text.Encodings.Web; using System.Text.Json.Nodes; using System.Threading.Tasks; -using Newtonsoft.Json; +using System.Diagnostics.CodeAnalysis; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -34,11 +34,8 @@ public async Task ObjectLoop() Employee angela = new Employee(); angela.Manager = angela; - // Compare parity with Newtonsoft.Json - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); // Ensure round-trip Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); @@ -51,10 +48,8 @@ public async Task ObjectArrayLoop() Employee angela = new Employee(); angela.Subordinates = new List { angela }; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(angelaCopy.Subordinates[0], angelaCopy); @@ -66,10 +61,8 @@ public async Task ObjectDictionaryLoop() Employee angela = new Employee(); angela.Contacts = new Dictionary { { "555-5555", angela } }; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(angelaCopy.Contacts["555-5555"], angelaCopy); @@ -84,10 +77,8 @@ public async Task ObjectPreserveDuplicateObjects() }; angela.Manager2 = angela.Manager; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(angelaCopy.Manager, angelaCopy.Manager2); @@ -102,10 +93,8 @@ public async Task ObjectPreserveDuplicateDictionaries() }; angela.Contacts2 = angela.Contacts; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(angelaCopy.Contacts, angelaCopy.Contacts2); @@ -120,10 +109,8 @@ public async Task ObjectPreserveDuplicateArrays() }; angela.Subordinates2 = angela.Subordinates; - string expected = JsonConvert.SerializeObject(angela, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(angela, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(angela, actual); Employee angelaCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(angelaCopy.Subordinates, angelaCopy.Subordinates2); @@ -253,10 +240,8 @@ public async Task DictionaryLoop() root["Self"] = root; root["Other"] = new DictionaryWithGenericCycle(); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); DictionaryWithGenericCycle rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy["Self"]); @@ -269,10 +254,8 @@ public async Task DictionaryPreserveDuplicateDictionaries() root["Self1"] = root; root["Self2"] = root; - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); DictionaryWithGenericCycle rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy["Self1"]); @@ -285,10 +268,8 @@ public async Task DictionaryObjectLoop() Dictionary root = new Dictionary(); root["Angela"] = new Employee() { Name = "Angela", Contacts = root }; - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); Dictionary rootCopy = await Serializer.DeserializeWrapper>(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy["Angela"].Contacts); @@ -302,10 +283,8 @@ public async Task DictionaryArrayLoop() DictionaryWithGenericCycleWithinList root = new DictionaryWithGenericCycleWithinList(); root["ArrayWithSelf"] = new List { root }; - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); DictionaryWithGenericCycleWithinList rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy["ArrayWithSelf"][0]); @@ -318,10 +297,8 @@ public async Task DictionaryPreserveDuplicateArrays() root["Array1"] = new List { root }; root["Array2"] = root["Array1"]; - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); DictionaryWithGenericCycleWithinList rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy["Array1"][0]); @@ -337,10 +314,8 @@ public async Task DictionaryPreserveDuplicateObjects() }; root["Employee2"] = root["Employee1"]; - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); Dictionary rootCopy = await Serializer.DeserializeWrapper>(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy["Employee1"], rootCopy["Employee2"]); @@ -447,10 +422,8 @@ public async Task ArrayLoop() ListWithGenericCycle root = new ListWithGenericCycle(); root.Add(root); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); ListWithGenericCycle rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy[0]); @@ -461,10 +434,8 @@ public async Task ArrayLoop() root.Add(root); root.Add(root); - expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy[0]); @@ -478,10 +449,8 @@ public async Task ArrayObjectLoop() List root = new List(); root.Add(new Employee() { Name = "Angela", Subordinates = root }); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); List rootCopy = await Serializer.DeserializeWrapper>(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy[0].Subordinates); @@ -496,10 +465,8 @@ public async Task ArrayPreserveDuplicateObjects() }; root.Add(root[0]); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); List rootCopy = await Serializer.DeserializeWrapper>(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy[0], rootCopy[1]); @@ -513,10 +480,8 @@ public async Task ArrayDictionaryLoop() ListWithGenericCycleWithinDictionary root = new ListWithGenericCycleWithinDictionary(); root.Add(new Dictionary { { "Root", root } }); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); ListWithGenericCycleWithinDictionary rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy, rootCopy[0]["Root"]); @@ -531,10 +496,8 @@ public async Task ArrayPreserveDuplicateDictionaries() }; root.Add(root[0]); - string expected = JsonConvert.SerializeObject(root, s_newtonsoftSerializerSettingsPreserve); string actual = await Serializer.SerializeWrapper(root, s_serializerOptionsPreserve); - - Assert.Equal(expected, actual); + AssertOutputEqualsNewtonsoft(root, actual); ListWithGenericCycleWithinDictionary rootCopy = await Serializer.DeserializeWrapper(actual, s_serializerOptionsPreserve); Assert.Same(rootCopy[0], rootCopy[1]); @@ -864,7 +827,7 @@ public IEnumerator GetEnumerator() [Theory] [InlineData(typeof(ClassWithConflictingRefProperty), "$ref")] [InlineData(typeof(ClassWithConflictingIdProperty), "$id")] - public async Task ClassWithConflictingMetadataProperties_ThrowsInvalidOperationException(Type type, string propertyName) + public async Task ClassWithConflictingMetadataProperties_ThrowsInvalidOperationException([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, string propertyName) { InvalidOperationException ex; object value = Activator.CreateInstance(type); diff --git a/src/libraries/System.Text.Json/tests/Common/RequiredKeywordTests.cs b/src/libraries/System.Text.Json/tests/Common/RequiredKeywordTests.cs index 9b70145773653d..9b51ffef337d50 100644 --- a/src/libraries/System.Text.Json/tests/Common/RequiredKeywordTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/RequiredKeywordTests.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -490,7 +491,9 @@ public class ClassWithRequiredField public required int RequiredField; } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + [RequiresUnreferencedCode("Uses JsonTypeInfo.CreateJsonPropertyInfo which requires reflection.")] + [RequiresDynamicCode("Uses JsonTypeInfo.CreateJsonPropertyInfo which requires reflection.")] public async Task RemovingPropertiesWithRequiredKeywordAllowsDeserialization() { JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(static ti => @@ -746,12 +749,7 @@ public ClassWithNullValidatingConstructor(string value) public string Value { get; } } - private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions options) - { - options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver; - options.MakeReadOnly(); - return options.GetTypeInfo(typeof(T)); - } + private JsonTypeInfo GetTypeInfo(JsonSerializerOptions options) => Serializer.GetTypeInfo(options); private static void AssertJsonTypeInfoHasRequiredProperties(JsonTypeInfo typeInfo, params string[] requiredProperties) { diff --git a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs index 084a88aa32b6ed..4b79d2991f600a 100644 --- a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs @@ -249,7 +249,7 @@ protected async Task TestMultiContextDeserialization( string wrappedJson; equalityComparer ??= expectedValue is IEquatable ? EqualityComparer.Default - : new JsonEqualityComparer(); + : new JsonEqualityComparer(Serializer.GetTypeInfo(options)); if (contexts.HasFlag(SerializedValueContext.RootValue)) { @@ -366,10 +366,39 @@ internal class GenericPoco public T Property { get; set; } } + // Creates a trim/AOT-safe equality comparer that compares values by serializing them + // using the resolved (polymorphism-aware) JsonTypeInfo and comparing the resulting JSON. + // Values of differing runtime types are never considered equal, preserving the behavior of + // the reflection-based structural comparer this replaced (so that, for example, a nearest- + // ancestor fallback regression cannot slip through when a derived type happens to serialize + // identically under its base contract). + protected IEqualityComparer CreateJsonEqualityComparer(JsonSerializerOptions? options = null) + => new JsonEqualityComparer(Serializer.GetTypeInfo(options), compareRuntimeType: true); + private class JsonEqualityComparer : IEqualityComparer { - public bool Equals(TValue? x, TValue? y) => JsonSerializer.Serialize(x) == JsonSerializer.Serialize(y); - public int GetHashCode([DisallowNull] TValue obj) => JsonSerializer.Serialize(obj).GetHashCode(); + private readonly JsonTypeInfo _jsonTypeInfo; + private readonly bool _compareRuntimeType; + + public JsonEqualityComparer(JsonTypeInfo jsonTypeInfo, bool compareRuntimeType = false) + { + _jsonTypeInfo = jsonTypeInfo; + _compareRuntimeType = compareRuntimeType; + } + + public bool Equals(TValue? x, TValue? y) + { + // Object.GetType() is trim/AOT-safe and lets us reject differing runtime types before + // falling back to structural (serialized JSON) comparison. + if (_compareRuntimeType && x is not null && y is not null && x.GetType() != y.GetType()) + { + return false; + } + + return JsonSerializer.Serialize(x, _jsonTypeInfo) == JsonSerializer.Serialize(y, _jsonTypeInfo); + } + + public int GetHashCode([DisallowNull] TValue obj) => JsonSerializer.Serialize(obj, _jsonTypeInfo).GetHashCode(); } private class ListAssertionEqualityComparer : IEqualityComparer> diff --git a/src/libraries/System.Text.Json/tests/Common/StructuralJsonTypeClassifierTests.cs b/src/libraries/System.Text.Json/tests/Common/StructuralJsonTypeClassifierTests.cs index b1131020528f34..ab4de54ead4885 100644 --- a/src/libraries/System.Text.Json/tests/Common/StructuralJsonTypeClassifierTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/StructuralJsonTypeClassifierTests.cs @@ -13,31 +13,26 @@ namespace System.Text.Json.Serialization.Tests { public abstract class StructuralJsonTypeClassifierTests(JsonSerializerWrapper serializerUnderTest) : SerializerTests(serializerUnderTest) { - private static readonly JsonSerializerOptions s_options = new() + private readonly JsonSerializerOptions _options = new(serializerUnderTest.DefaultOptions); + private readonly JsonSerializerOptions _caseInsensitiveOptions = new(serializerUnderTest.DefaultOptions) { - TypeInfoResolver = new DefaultJsonTypeInfoResolver() + PropertyNameCaseInsensitive = true }; - private static readonly JsonSerializerOptions s_caseInsensitiveOptions = new() + private readonly JsonSerializerOptions _numberFromStringOptions = new(serializerUnderTest.DefaultOptions) { - PropertyNameCaseInsensitive = true, - TypeInfoResolver = new DefaultJsonTypeInfoResolver() - }; - private static readonly JsonSerializerOptions s_numberFromStringOptions = new() - { - NumberHandling = JsonNumberHandling.AllowReadingFromString, - TypeInfoResolver = new DefaultJsonTypeInfoResolver() + NumberHandling = JsonNumberHandling.AllowReadingFromString }; [Fact] public async Task StructuralClassifier_DistinguishesObjectProperties() { - PetUnion? dog = await Serializer.DeserializeWrapper("""{"Name":"Rex","Breed":"Labrador"}""", s_options); + PetUnion? dog = await Serializer.DeserializeWrapper("""{"Name":"Rex","Breed":"Labrador"}""", _options); Assert.NotNull(dog); Dog dogValue = Assert.IsType(GetUnionValue(dog)); Assert.Equal("Rex", dogValue.Name); Assert.Equal("Labrador", dogValue.Breed); - PetUnion? cat = await Serializer.DeserializeWrapper("""{"Name":"Misty","Lives":9}""", s_options); + PetUnion? cat = await Serializer.DeserializeWrapper("""{"Name":"Misty","Lives":9}""", _options); Assert.NotNull(cat); Cat catValue = Assert.IsType(GetUnionValue(cat)); Assert.Equal("Misty", catValue.Name); @@ -47,15 +42,15 @@ public async Task StructuralClassifier_DistinguishesObjectProperties() [Fact] public async Task StructuralClassifier_DistinguishesStringPatterns() { - TemporalUnion? date = await Serializer.DeserializeWrapper("\"2026-05-11T18:00:00Z\"", s_options); + TemporalUnion? date = await Serializer.DeserializeWrapper("\"2026-05-11T18:00:00Z\"", _options); Assert.NotNull(date); Assert.IsType(GetUnionValue(date)); - TemporalUnion? duration = await Serializer.DeserializeWrapper("\"01:02:03\"", s_options); + TemporalUnion? duration = await Serializer.DeserializeWrapper("\"01:02:03\"", _options); Assert.NotNull(duration); Assert.Equal(TimeSpan.FromSeconds(3723), Assert.IsType(GetUnionValue(duration))); - TemporalUnion? guid = await Serializer.DeserializeWrapper("\"0f8fad5b-d9cb-469f-a165-70867728950e\"", s_options); + TemporalUnion? guid = await Serializer.DeserializeWrapper("\"0f8fad5b-d9cb-469f-a165-70867728950e\"", _options); Assert.NotNull(guid); Assert.Equal(new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), Assert.IsType(GetUnionValue(guid))); } @@ -63,11 +58,11 @@ public async Task StructuralClassifier_DistinguishesStringPatterns() [Fact] public async Task StructuralClassifier_SamplesCollectionElementShapes() { - ListUnion? numbers = await Serializer.DeserializeWrapper("[1,2,3]", s_options); + ListUnion? numbers = await Serializer.DeserializeWrapper("[1,2,3]", _options); Assert.NotNull(numbers); Assert.Equal([1, 2, 3], Assert.IsType>(GetUnionValue(numbers))); - ListUnion? strings = await Serializer.DeserializeWrapper("""["one","two"]""", s_options); + ListUnion? strings = await Serializer.DeserializeWrapper("""["one","two"]""", _options); Assert.NotNull(strings); Assert.Equal(["one", "two"], Assert.IsType>(GetUnionValue(strings))); } @@ -75,11 +70,11 @@ public async Task StructuralClassifier_SamplesCollectionElementShapes() [Fact] public async Task StructuralClassifier_SamplesDictionaryValueShapes() { - DictionaryUnion? numbers = await Serializer.DeserializeWrapper("""{"one":1,"two":2}""", s_options); + DictionaryUnion? numbers = await Serializer.DeserializeWrapper("""{"one":1,"two":2}""", _options); Assert.NotNull(numbers); Assert.Equal(2, Assert.IsType>(GetUnionValue(numbers))["two"]); - DictionaryUnion? strings = await Serializer.DeserializeWrapper("""{"one":"uno","two":"dos"}""", s_options); + DictionaryUnion? strings = await Serializer.DeserializeWrapper("""{"one":"uno","two":"dos"}""", _options); Assert.NotNull(strings); Assert.Equal("dos", Assert.IsType>(GetUnionValue(strings))["two"]); } @@ -87,12 +82,12 @@ public async Task StructuralClassifier_SamplesDictionaryValueShapes() [Fact] public async Task StructuralClassifier_RecursesIntoNestedGenericShapes() { - BatchUnion? temperatures = await Serializer.DeserializeWrapper("""{"Source":"sensor","Items":[{"Celsius":21.5}]}""", s_options); + BatchUnion? temperatures = await Serializer.DeserializeWrapper("""{"Source":"sensor","Items":[{"Celsius":21.5}]}""", _options); Assert.NotNull(temperatures); Batch temperatureBatch = Assert.IsType>(GetUnionValue(temperatures)); Assert.Equal(21.5, temperatureBatch.Items![0].Celsius); - BatchUnion? statuses = await Serializer.DeserializeWrapper("""{"Source":"sensor","Items":[{"IsOnline":true}]}""", s_options); + BatchUnion? statuses = await Serializer.DeserializeWrapper("""{"Source":"sensor","Items":[{"IsOnline":true}]}""", _options); Assert.NotNull(statuses); Batch statusBatch = Assert.IsType>(GetUnionValue(statuses)); Assert.True(statusBatch.Items![0].IsOnline); @@ -101,11 +96,11 @@ public async Task StructuralClassifier_RecursesIntoNestedGenericShapes() [Fact] public async Task StructuralClassifier_DistinguishesArrayFromDictionary() { - ArrayOrDictionaryUnion? array = await Serializer.DeserializeWrapper("[1,2,3]", s_options); + ArrayOrDictionaryUnion? array = await Serializer.DeserializeWrapper("[1,2,3]", _options); Assert.NotNull(array); Assert.Equal([1, 2, 3], Assert.IsType>(GetUnionValue(array))); - ArrayOrDictionaryUnion? dictionary = await Serializer.DeserializeWrapper("""{"one":1,"two":2}""", s_options); + ArrayOrDictionaryUnion? dictionary = await Serializer.DeserializeWrapper("""{"one":1,"two":2}""", _options); Assert.NotNull(dictionary); Assert.Equal(2, Assert.IsType>(GetUnionValue(dictionary))["two"]); } @@ -113,7 +108,7 @@ public async Task StructuralClassifier_DistinguishesArrayFromDictionary() [Fact] public async Task StructuralClassifier_PrefersObjectShapeOverDictionaryWhenPropertiesMatch() { - ObjectOrDictionaryUnion? result = await Serializer.DeserializeWrapper("""{"X":1,"Y":2}""", s_options); + ObjectOrDictionaryUnion? result = await Serializer.DeserializeWrapper("""{"X":1,"Y":2}""", _options); Assert.NotNull(result); Point point = Assert.IsType(GetUnionValue(result)); Assert.Equal(1, point.X); @@ -123,7 +118,7 @@ public async Task StructuralClassifier_PrefersObjectShapeOverDictionaryWhenPrope [Fact] public async Task StructuralClassifier_UsesJsonTypeInfoPropertyNames() { - RenamedPropertyUnion? result = await Serializer.DeserializeWrapper("""{"kind":"special"}""", s_options); + RenamedPropertyUnion? result = await Serializer.DeserializeWrapper("""{"kind":"special"}""", _options); Assert.NotNull(result); RenamedPropertyCase value = Assert.IsType(GetUnionValue(result)); Assert.Equal("special", value.Kind); @@ -132,7 +127,7 @@ public async Task StructuralClassifier_UsesJsonTypeInfoPropertyNames() [Fact] public async Task StructuralClassifier_UsesCaseInsensitivePropertyNames() { - PetUnion? dog = await Serializer.DeserializeWrapper("""{"name":"Rex","breed":"Labrador"}""", s_caseInsensitiveOptions); + PetUnion? dog = await Serializer.DeserializeWrapper("""{"name":"Rex","breed":"Labrador"}""", _caseInsensitiveOptions); Assert.NotNull(dog); Dog dogValue = Assert.IsType(GetUnionValue(dog)); Assert.Equal("Rex", dogValue.Name); @@ -142,7 +137,7 @@ public async Task StructuralClassifier_UsesCaseInsensitivePropertyNames() [Fact] public async Task StructuralClassifier_UsesNumberHandlingMetadata() { - NumberHandlingUnion? result = await Serializer.DeserializeWrapper("\"42\"", s_numberFromStringOptions); + NumberHandlingUnion? result = await Serializer.DeserializeWrapper("\"42\"", _numberFromStringOptions); Assert.NotNull(result); Assert.Equal(42, Assert.IsType(GetUnionValue(result))); } @@ -155,17 +150,17 @@ public async Task StructuralClassifier_UsesNumberHandlingMetadata() [InlineData("true", typeof(PetUnion))] public async Task StructuralClassifier_AmbiguousOrUnsupportedShapesThrow(string json, Type unionType) { - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json, unionType, s_options)); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json, unionType, _options)); } [Fact] public async Task StructuralClassifier_WorksForPolymorphicDerivedTypeMetadata() { - Shape? circle = await Serializer.DeserializeWrapper("""{"Color":"red","Radius":2.5}""", s_options); + Shape? circle = await Serializer.DeserializeWrapper("""{"Color":"red","Radius":2.5}""", _options); Assert.NotNull(circle); Assert.Equal(2.5, Assert.IsType(circle).Radius); - Shape? rectangle = await Serializer.DeserializeWrapper("""{"Color":"blue","Width":3,"Height":4}""", s_options); + Shape? rectangle = await Serializer.DeserializeWrapper("""{"Color":"blue","Width":3,"Height":4}""", _options); Assert.NotNull(rectangle); Assert.Equal(4, Assert.IsType(rectangle).Height); } diff --git a/src/libraries/System.Text.Json/tests/Common/UnionTests.cs b/src/libraries/System.Text.Json/tests/Common/UnionTests.cs index d5996eef7e1899..ad73d52ce1b582 100644 --- a/src/libraries/System.Text.Json/tests/Common/UnionTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/UnionTests.cs @@ -182,7 +182,7 @@ public void PerTypeTypeClassifier_IsVisibleToModifier() // Trigger configuration of the type info via GetTypeInfo (modifiers run during // resolver invocation, before Configure). - options.MakeReadOnly(populateMissingResolver: true); + options.MakeReadOnly(); _ = options.GetTypeInfo(typeof(UnionWithCustomClassifier)); Assert.NotNull(observedAtModifierTime); @@ -323,7 +323,7 @@ public void OptionsTypeClassifier_IsVisibleToModifier() }); options.TypeClassifiers.Add(new UnionWithOtherCaseOptionsClassifierFactory()); - options.MakeReadOnly(populateMissingResolver: true); + options.MakeReadOnly(); _ = options.GetTypeInfo(typeof(UnionWithCustomConverterCase)); Assert.NotNull(observedAtModifierTime); @@ -1140,7 +1140,7 @@ public void Configure_ThrowsWhenModifierClearsUnionConstructor() } }); - options.MakeReadOnly(populateMissingResolver: true); + options.MakeReadOnly(); InvalidOperationException ex = Assert.Throws( () => options.GetTypeInfo(typeof(NullableCaseUnion))); @@ -1160,7 +1160,7 @@ public void Configure_ThrowsWhenModifierClearsUnionDeconstructor() } }); - options.MakeReadOnly(populateMissingResolver: true); + options.MakeReadOnly(); InvalidOperationException ex = Assert.Throws( () => options.GetTypeInfo(typeof(NullableCaseUnion))); @@ -1779,7 +1779,7 @@ public void CyclicUnion_Serialize_ThrowsJsonExceptionAtMaxDepth() }; Assert.Throws(() => - JsonSerializer.Serialize(union, options)); + JsonSerializer.Serialize(union, options.GetTypeInfo())); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs b/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs index 37473029fc0f8e..ce8978778c2af6 100644 --- a/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.Serialization; using System.Threading; @@ -168,9 +169,10 @@ public override void Write(Utf8JsonWriter writer, IntPtr value, JsonSerializerOp } } -#if !BUILDING_SOURCE_GENERATOR_TESTS [Fact] [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp, "NullableContextAttribute is public in corelib in .NET 8+")] + [RequiresUnreferencedCode("Uses reflection to construct and serialize an arbitrary runtime type.")] + [RequiresDynamicCode("Uses reflection to construct and serialize an arbitrary runtime type.")] public async Task TypeWithNullConstructorParameterName_ThrowsNotSupportedException() { // Regression test for https://github.com/dotnet/runtime/issues/58690 @@ -182,7 +184,6 @@ public async Task TypeWithNullConstructorParameterName_ThrowsNotSupportedExcepti await Assert.ThrowsAnyAsync(() => Serializer.SerializeWrapper(value)); await Assert.ThrowsAnyAsync(() => Serializer.DeserializeWrapper("{}", type)); } -#endif [Fact] public async Task RuntimeConverterIsSupported_IntPtr() diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 88f59426a2105e..133756c4d9eb27 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -121,6 +122,8 @@ static void AssertGenericContext(JsonSerializerContext context) } [Fact] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static async Task SupportsBoxedRootLevelValues() { PersonJsonContext context = PersonJsonContext.Default; @@ -293,6 +296,8 @@ public static void CombiningContexts_ResolveJsonTypeInfo_DifferentCasing() } [Fact] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void FastPathSerialization_ResolvingJsonTypeInfo() { JsonSerializerOptions options = FastPathSerializationContext.Default.Options; @@ -314,6 +319,8 @@ public static void FastPathSerialization_ResolvingJsonTypeInfo() [Theory] [MemberData(nameof(GetFastPathCompatibleResolvers))] [MemberData(nameof(GetFastPathIncompatibleResolvers))] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void FastPathSerialization_AppendedResolver_WorksAsExpected(IJsonTypeInfoResolver appendedResolver) { // Resolvers appended after ours will never introduce metadata to the type graph, @@ -356,6 +363,8 @@ public static void FastPathSerialization_AppendedResolver_WorksAsExpected(IJsonT [Theory] [MemberData(nameof(GetFastPathCompatibleResolvers))] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void FastPathSerialization_PrependedResolver_CompatibleResolvers_WorksAsExpected(IJsonTypeInfoResolver prependedResolver) { // We're prepending a resolver that generates metadata for the property of our type, @@ -398,6 +407,8 @@ public static void FastPathSerialization_PrependedResolver_CompatibleResolvers_W [Theory] [MemberData(nameof(GetFastPathIncompatibleResolvers))] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void FastPathSerialization_PrependedResolver_IncompatibleResolvers_FallsBackToMetadata(IJsonTypeInfoResolver prependedResolver) { // We're prepending a resolver that generates metadata for the property of our type, @@ -438,6 +449,8 @@ public static void FastPathSerialization_PrependedResolver_IncompatibleResolvers Assert.Equal(0, fastPathContext.FastPathInvocationCount); } + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static IEnumerable GetFastPathCompatibleResolvers() { yield return new object[] { CompatibleWithInstrumentedFastPathContext.Default }; @@ -446,6 +459,8 @@ public static IEnumerable GetFastPathCompatibleResolvers() yield return new object[] { new CustomWrappingResolver { Resolver = new ContextWithInstrumentedFastPath() } }; } + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static IEnumerable GetFastPathIncompatibleResolvers() { yield return new object[] { NotCompatibleWithInstrumentedFastPathContext.Default }; @@ -541,6 +556,8 @@ public partial class FastPathSerializationContext : JsonSerializerContext [Theory] [MemberData(nameof(GetCombiningContextsData))] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void CombiningContexts_Serialization(T value, string expectedJson) { IJsonTypeInfoResolver combined = JsonTypeInfoResolver.Combine(NestedContext.Default, PersonJsonContext.Default); @@ -560,6 +577,8 @@ public static void CombiningContexts_Serialization(T value, string expectedJs [Theory] [MemberData(nameof(GetCombiningContextsData))] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void ChainedContexts_Serialization(T value, string expectedJson) { var options = new JsonSerializerOptions { TypeInfoResolverChain = { NestedContext.Default, PersonJsonContext.Default } }; @@ -577,6 +596,8 @@ public static void ChainedContexts_Serialization(T value, string expectedJson } [Fact] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void CombiningContextWithCustomResolver_ReplacePoco() { TestResolver customResolver = new((type, options) => @@ -725,7 +746,7 @@ public static void ClassWithStringValuesRoundtrips() StringValuesProperty = new(new[] { "abc", "def" }) }; - string json = JsonSerializer.Serialize(obj, options); + string json = JsonSerializer.Serialize(obj, options.GetTypeInfo()); Assert.Equal("""{"StringValuesProperty":["abc","def"]}""", json); } @@ -741,7 +762,7 @@ public static void ClassWithDictionaryPropertyRoundtrips() ["test"] = "baz", }); - string json = JsonSerializer.Serialize(obj, options); + string json = JsonSerializer.Serialize(obj, options.GetTypeInfo()); Assert.Equal("""{"DictionaryProperty":{"foo":"bar","test":"baz"}}""", json); } @@ -819,6 +840,8 @@ public TestResolver(Func getTypeInfo } [Fact] + [RequiresUnreferencedCode("Tests reflection-based JsonSerializer APIs.")] + [RequiresDynamicCode("Tests reflection-based JsonSerializer APIs.")] public static void FastPathSerialization_EvaluatePropertyOnlyOnceWhenIgnoreNullOrDefaultIsSpecified() { JsonSerializerOptions options = FastPathSerializationContext.Default.Options; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs index 083247c10ceed6..2063fe0cfec491 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json.Serialization; using Xunit; @@ -10,6 +11,7 @@ namespace System.Text.Json.SourceGeneration.Tests public static partial class JsonSourceGenerationOptionsTests { [Fact] + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void ContextWithGeneralSerializerDefaults_GeneratesExpectedOptions() { JsonSerializerOptions expected = new(JsonSerializerDefaults.General) { TypeInfoResolver = ContextWithGeneralSerializerDefaults.Default }; @@ -24,6 +26,7 @@ public partial class ContextWithGeneralSerializerDefaults : JsonSerializerContex { } [Fact] + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void ContextWithWebSerializerDefaults_GeneratesExpectedOptions() { JsonSerializerOptions expected = new(JsonSerializerDefaults.Web) { TypeInfoResolver = ContextWithWebSerializerDefaults.Default }; @@ -38,6 +41,7 @@ public partial class ContextWithWebSerializerDefaults : JsonSerializerContext { } [Fact] + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void ContextWithStrictSerializerDefaults_GeneratesExpectedOptions() { JsonSerializerOptions expected = new(JsonSerializerDefaults.Strict) { TypeInfoResolver = ContextWithStrictSerializerDefaults.Default }; @@ -52,6 +56,7 @@ public partial class ContextWithStrictSerializerDefaults : JsonSerializerContext { } [Fact] + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy_GeneratesExpectedOptions() { JsonSerializerOptions expected = new(JsonSerializerDefaults.Web) @@ -71,6 +76,7 @@ public partial class ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy : J { } [Fact] + [RequiresUnreferencedCode("AssertOptionsEqual uses reflection to enumerate JsonSerializerOptions properties.")] public static void ContextWithAllOptionsSet_GeneratesExpectedOptions() { JsonSerializerOptions expected = new(JsonSerializerDefaults.Web) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs index 3cfcdcdc03a7e9..bf96dc4714c45f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs @@ -777,7 +777,7 @@ public class MyNestedNestedClass [Fact] public void ConstructingFromOptionsKeepsReference() { - JsonStringEnumConverter converter = new(); + JsonConverter converter = new JsonStringEnumConverter(); JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true, diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs index b54ad5c16db648..2822d6a5b59882 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs @@ -23,10 +23,10 @@ public StringSerializerWrapper(JsonSerializerContext defaultContext) public override JsonSerializerOptions DefaultOptions => _defaultContext.Options; public override Task SerializeWrapper(object value, Type type, JsonSerializerOptions? options = null) - => Task.FromResult(JsonSerializer.Serialize(value, type, GetOptions(options))); + => Task.FromResult(JsonSerializer.Serialize(value, GetOptions(options).GetTypeInfo(type))); public override Task SerializeWrapper(T value, JsonSerializerOptions? options = null) - => Task.FromResult(JsonSerializer.Serialize(value, GetOptions(options))); + => Task.FromResult(JsonSerializer.Serialize(value, GetOptions(options).GetTypeInfo())); public override Task SerializeWrapper(object value, Type inputType, JsonSerializerContext context) => Task.FromResult(JsonSerializer.Serialize(value, inputType, context)); @@ -38,10 +38,10 @@ public override Task SerializeWrapper(object value, JsonTypeInfo jsonTyp => Task.FromResult(JsonSerializer.Serialize(value, jsonTypeInfo)); public override Task DeserializeWrapper(string json, JsonSerializerOptions? options = null) - => Task.FromResult(JsonSerializer.Deserialize(json, GetOptions(options))); + => Task.FromResult(JsonSerializer.Deserialize(json, GetOptions(options).GetTypeInfo())); public override Task DeserializeWrapper(string json, Type type, JsonSerializerOptions? options = null) - => Task.FromResult(JsonSerializer.Deserialize(json, type, GetOptions(options))); + => Task.FromResult(JsonSerializer.Deserialize(json, GetOptions(options).GetTypeInfo(type))); public override Task DeserializeWrapper(string json, JsonTypeInfo jsonTypeInfo) => Task.FromResult(JsonSerializer.Deserialize(json, jsonTypeInfo)); @@ -71,7 +71,7 @@ private JsonSerializerOptions GetOptions(JsonSerializerOptions? options = null) public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options), cancellationToken); + return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options).GetTypeInfo(), cancellationToken); } public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) @@ -86,7 +86,7 @@ public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Jso public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, bool topLevelValues, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, topLevelValues, GetOptions(options), cancellationToken); + return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options).GetTypeInfo(), topLevelValues, cancellationToken); } } @@ -104,12 +104,12 @@ public AsyncStreamSerializerWrapper(JsonSerializerContext defaultContext) public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, GetOptions(options), cancellationToken); + return await JsonSerializer.DeserializeAsync(utf8Json, GetOptions(options).GetTypeInfo(), cancellationToken); } public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, returnType, GetOptions(options), cancellationToken); + return await JsonSerializer.DeserializeAsync(utf8Json, GetOptions(options).GetTypeInfo(returnType), cancellationToken); } public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) @@ -128,10 +128,10 @@ public override async Task DeserializeWrapper(Stream utf8Json, Type retu } public override Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) - => JsonSerializer.SerializeAsync(stream, value, GetOptions(options), cancellationToken); + => JsonSerializer.SerializeAsync(stream, value, GetOptions(options).GetTypeInfo(), cancellationToken); public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) - => JsonSerializer.SerializeAsync(stream, value, inputType, GetOptions(options), cancellationToken); + => JsonSerializer.SerializeAsync(stream, value, GetOptions(options).GetTypeInfo(inputType), cancellationToken); public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo, cancellationToken); @@ -161,7 +161,7 @@ private JsonSerializerOptions GetOptions(JsonSerializerOptions? options = null) public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options), cancellationToken); + return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options).GetTypeInfo(), cancellationToken); } public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) @@ -176,7 +176,7 @@ public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Jso public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, bool topLevelValues, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, topLevelValues, GetOptions(options), cancellationToken); + return JsonSerializer.DeserializeAsyncEnumerable(utf8Json, GetOptions(options).GetTypeInfo(), topLevelValues, cancellationToken); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs index 78c2aa445ce340..21e63f95e7cd10 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs @@ -241,6 +241,12 @@ public PolymorphicTests_Metadata() [JsonSerializable(typeof(PolymorphicTests.MyClass))] [JsonSerializable(typeof(PolymorphicTests.MyThingCollection))] [JsonSerializable(typeof(PolymorphicTests.MyThingDictionary))] + [JsonSerializable(typeof(PolymorphicTests.MyDerivedThing))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(ISet))] + [JsonSerializable(typeof(PolymorphicInterface.DerivedInterface1))] + [JsonSerializable(typeof(PolymorphicInterface.DerivedInterface2))] internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext { } @@ -486,6 +492,7 @@ public PolymorphicTests_Default() [JsonSerializable(typeof(PolymorphicTests.MyClass))] [JsonSerializable(typeof(PolymorphicTests.MyThingCollection))] [JsonSerializable(typeof(PolymorphicTests.MyThingDictionary))] + [JsonSerializable(typeof(PolymorphicTests.MyDerivedThing))] internal sealed partial class PolymorphicTestsContext_Default : JsonSerializerContext { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index a5e6b752e20a0f..c7bd164df930ec 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text.Json.Serialization; @@ -27,7 +28,7 @@ protected PropertyVisibilityTests_Metadata(Serialization.Tests.JsonSerializerWra [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] - public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { InvalidOperationException ioe = await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("", type)); ValidateInvalidOperationException(); @@ -65,7 +66,7 @@ public override async Task Honor_JsonSerializablePropertyAttribute_OnProperties( [InlineData(typeof(Class_PropertyWith_PrivateInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute))] [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] - public override async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) + public override async Task NonPublicInitOnlySetter_With_JsonInclude([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { // Inaccessible [JsonInclude] members are ignored (https://github.com/dotnet/runtime/issues/124889). // All types have public getter so serialization works; internal setter type can also deserialize. @@ -94,7 +95,7 @@ public override async Task HonorCustomConverter_UsingPrivateSetter() // MyEnum (private get, public set): setter works, getter excluded. // MyInt (public get, private set): getter works (with converter), setter excluded. var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonStringEnumConverter()); string json = """{"MyEnum":"AnotherValue","MyInt":2}"""; var obj = await Serializer.DeserializeWrapper(json, options); @@ -147,7 +148,7 @@ public override async Task HonorJsonPropertyName_PrivateSetter() Assert.Equal(0, obj.MyInt); } - public override async Task NonPublicProperty_JsonInclude_WorksAsExpected(Type type, bool isAccessibleBySourceGen) + public override async Task NonPublicProperty_JsonInclude_WorksAsExpected([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, bool isAccessibleBySourceGen) { // Inaccessible [JsonInclude] members are ignored (https://github.com/dotnet/runtime/issues/124889). if (isAccessibleBySourceGen) @@ -580,7 +581,7 @@ public PropertyVisibilityTests_Default() [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] - public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail(Type type) + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("{}", type)); await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(Activator.CreateInstance(type), type)); @@ -589,7 +590,7 @@ public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail( [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] - public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson(Type type) + public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_EmptyJson([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { // Since this code goes down fast-path, there's no warm up and we hit the reader exception about having no tokens. await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("", type)); @@ -612,15 +613,15 @@ public void PublicContextAndTestClassWithPropertiesWithDifferentAccessibilities( PublicField = new(), }; - string json = JsonSerializer.Serialize(obj, options); + string json = JsonSerializer.Serialize(obj, options.GetTypeInfo()); Assert.Equal("""{"PublicProperty":{},"PublicField":{}}""", json); - var deserialized = JsonSerializer.Deserialize(json, options); + var deserialized = JsonSerializer.Deserialize(json, options.GetTypeInfo()); Assert.NotNull(deserialized.PublicProperty); Assert.NotNull(deserialized.PublicField); json = "{}"; - deserialized = JsonSerializer.Deserialize(json, options); + deserialized = JsonSerializer.Deserialize(json, options.GetTypeInfo()); Assert.Null(deserialized.PublicProperty); Assert.Null(deserialized.PublicField); } @@ -630,8 +631,8 @@ public void PublicContextAndJsonConverter() { JsonConverter obj = JsonMetadataServices.BooleanConverter; - Assert.Throws(() => JsonSerializer.Serialize(obj, PublicContext.Default.Options)); - Assert.Throws(() => JsonSerializer.Deserialize("{}", PublicContext.Default.Options)); + Assert.Throws(() => JsonSerializer.Serialize(obj, PublicContext.Default.Options.GetTypeInfo())); + Assert.Throws(() => JsonSerializer.Deserialize("{}", PublicContext.Default.Options.GetTypeInfo())); } [Fact] @@ -644,9 +645,9 @@ public void PublicContextAndJsonSerializerOptions() IncludeFields = true, }; - string json = JsonSerializer.Serialize(obj, PublicContext.Default.Options); + string json = JsonSerializer.Serialize(obj, PublicContext.Default.Options.GetTypeInfo()); - JsonSerializerOptions deserialized = JsonSerializer.Deserialize(json, PublicContext.Default.Options); + JsonSerializerOptions deserialized = JsonSerializer.Deserialize(json, PublicContext.Default.Options.GetTypeInfo()); Assert.Equal(obj.DefaultBufferSize, deserialized.DefaultBufferSize); Assert.Equal(obj.DefaultIgnoreCondition, deserialized.DefaultIgnoreCondition); Assert.Equal(obj.IncludeFields, deserialized.IncludeFields); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs index 2e4b94b952fcf3..4e8c4677c239ba 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs @@ -83,7 +83,7 @@ public static IEnumerable GetCompatibleOptions() // Options with features that aren't supported in the generated serialization funcs. public static IEnumerable GetOptionsUsingUnsupportedFeatures() { - yield return new object[] { new JsonSerializerOptions(s_compatibleOptions) { Converters = { new JsonStringEnumConverter() } } }; + yield return new object[] { new JsonSerializerOptions(s_compatibleOptions) { Converters = { new JsonStringEnumConverter() } } }; yield return new object[] { new JsonSerializerOptions(s_compatibleOptions) { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping } }; yield return new object[] { new JsonSerializerOptions(s_compatibleOptions) { NumberHandling = JsonNumberHandling.WriteAsString } }; yield return new object[] { new JsonSerializerOptions(s_compatibleOptions) { NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals } }; @@ -101,7 +101,7 @@ public static IEnumerable GetOptionsUsingUnsupportedFeatures() public static IEnumerable GetIncompatibleOptions() { yield return new object[] { new JsonSerializerOptions() }; - yield return new object[] { new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } }; + yield return new object[] { new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } }; yield return new object[] { new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping } }; yield return new object[] { new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString } }; yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve } }; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj index ccdf792ab9b8e4..bde7513156c634 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj @@ -2,9 +2,6 @@ 3.11 - - false - false diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj index 21aa274d363219..0fa1bccdb970ba 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj @@ -2,9 +2,6 @@ 4.4 - - false - false diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index 2233ab2704d2e9..1870e21a52c758 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -174,8 +174,13 @@ + + + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs index a049400c652092..88ccb4ed695aae 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.SourceGeneration.Tests { @@ -336,7 +337,7 @@ public override Option Read(ref Utf8JsonReader reader, Type typeToConvert, Js return default; } - return new(JsonSerializer.Deserialize(ref reader, options)!); + return new(JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!); } public override void Write(Utf8JsonWriter writer, Option value, JsonSerializerOptions options) @@ -347,7 +348,7 @@ public override void Write(Utf8JsonWriter writer, Option value, JsonSerialize return; } - JsonSerializer.Serialize(writer, value.Value, options); + JsonSerializer.Serialize(writer, value.Value, options.GetTypeInfo()); } } @@ -380,13 +381,13 @@ public sealed class GenericWrapperConverter : JsonConverter { public override GenericWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - T value = JsonSerializer.Deserialize(ref reader, options)!; + T value = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; return new GenericWrapper(value); } public override void Write(Utf8JsonWriter writer, GenericWrapper value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value.WrappedValue, options); + JsonSerializer.Serialize(writer, value.WrappedValue, options.GetTypeInfo()); } } @@ -430,9 +431,9 @@ public override TypeWithNestedConverter Read(ref Utf8JsonReader reader, Ty reader.Read(); if (propertyName == "Value1") - result.Value1 = JsonSerializer.Deserialize(ref reader, options)!; + result.Value1 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; else if (propertyName == "Value2") - result.Value2 = JsonSerializer.Deserialize(ref reader, options)!; + result.Value2 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; } return result; @@ -442,9 +443,9 @@ public override void Write(Utf8JsonWriter writer, TypeWithNestedConverter { writer.WriteStartObject(); writer.WritePropertyName("Value1"); - JsonSerializer.Serialize(writer, value.Value1, options); + JsonSerializer.Serialize(writer, value.Value1, options.GetTypeInfo()); writer.WritePropertyName("Value2"); - JsonSerializer.Serialize(writer, value.Value2, options); + JsonSerializer.Serialize(writer, value.Value2, options.GetTypeInfo()); writer.WriteEndObject(); } } @@ -477,7 +478,7 @@ public override TypeWithSatisfiedConstraint Read(ref Utf8JsonReader reader, T reader.Read(); if (propertyName == "Value") - result.Value = JsonSerializer.Deserialize(ref reader, options)!; + result.Value = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; } return result; @@ -487,7 +488,7 @@ public override void Write(Utf8JsonWriter writer, TypeWithSatisfiedConstraint { writer.WriteStartObject(); writer.WritePropertyName("Value"); - JsonSerializer.Serialize(writer, value.Value, options); + JsonSerializer.Serialize(writer, value.Value, options.GetTypeInfo()); writer.WriteEndObject(); } } @@ -524,9 +525,9 @@ public override TypeWithDeeplyNestedConverter Read(ref Utf8JsonReader read reader.Read(); if (propertyName == "Value1") - result.Value1 = JsonSerializer.Deserialize(ref reader, options)!; + result.Value1 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; else if (propertyName == "Value2") - result.Value2 = JsonSerializer.Deserialize(ref reader, options)!; + result.Value2 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; } return result; @@ -536,9 +537,9 @@ public override void Write(Utf8JsonWriter writer, TypeWithDeeplyNestedConverter< { writer.WriteStartObject(); writer.WritePropertyName("Value1"); - JsonSerializer.Serialize(writer, value.Value1, options); + JsonSerializer.Serialize(writer, value.Value1, options.GetTypeInfo()); writer.WritePropertyName("Value2"); - JsonSerializer.Serialize(writer, value.Value2, options); + JsonSerializer.Serialize(writer, value.Value2, options.GetTypeInfo()); writer.WriteEndObject(); } } @@ -581,11 +582,11 @@ public override TypeWithManyParams Read(ref Utf8JsonReader reader switch (propertyName) { - case "Value1": result.Value1 = JsonSerializer.Deserialize(ref reader, options)!; break; - case "Value2": result.Value2 = JsonSerializer.Deserialize(ref reader, options)!; break; - case "Value3": result.Value3 = JsonSerializer.Deserialize(ref reader, options)!; break; - case "Value4": result.Value4 = JsonSerializer.Deserialize(ref reader, options)!; break; - case "Value5": result.Value5 = JsonSerializer.Deserialize(ref reader, options)!; break; + case "Value1": result.Value1 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; break; + case "Value2": result.Value2 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; break; + case "Value3": result.Value3 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; break; + case "Value4": result.Value4 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; break; + case "Value5": result.Value5 = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; break; } } @@ -596,15 +597,15 @@ public override void Write(Utf8JsonWriter writer, TypeWithManyParams()); writer.WritePropertyName("Value2"); - JsonSerializer.Serialize(writer, value.Value2, options); + JsonSerializer.Serialize(writer, value.Value2, options.GetTypeInfo()); writer.WritePropertyName("Value3"); - JsonSerializer.Serialize(writer, value.Value3, options); + JsonSerializer.Serialize(writer, value.Value3, options.GetTypeInfo()); writer.WritePropertyName("Value4"); - JsonSerializer.Serialize(writer, value.Value4, options); + JsonSerializer.Serialize(writer, value.Value4, options.GetTypeInfo()); writer.WritePropertyName("Value5"); - JsonSerializer.Serialize(writer, value.Value5, options); + JsonSerializer.Serialize(writer, value.Value5, options.GetTypeInfo()); writer.WriteEndObject(); } } @@ -640,7 +641,7 @@ public override TypeWithSingleLevelNestedConverter Read(ref Utf8JsonReader re reader.Read(); if (propertyName == "Value") - result.Value = JsonSerializer.Deserialize(ref reader, options)!; + result.Value = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo())!; } return result; @@ -650,7 +651,7 @@ public override void Write(Utf8JsonWriter writer, TypeWithSingleLevelNestedConve { writer.WriteStartObject(); writer.WritePropertyName("Value"); - JsonSerializer.Serialize(writer, value.Value, options); + JsonSerializer.Serialize(writer, value.Value, options.GetTypeInfo()); writer.WriteEndObject(); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs index e0da363e9d8d74..4f30b99bca8edb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs @@ -994,7 +994,7 @@ public static void JsonValue_CreateFromDouble() [Fact] public static void JsonValue_CreateWithJsonTypeInfo() { - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(int)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); JsonValue value = JsonValue.Create(42, typeInfo); Assert.Equal(42, value.GetValue()); @@ -1003,7 +1003,7 @@ public static void JsonValue_CreateWithJsonTypeInfo() [Fact] public static void JsonValue_CreateWithJsonTypeInfoAndOptions() { - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(string)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); var nodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true }; JsonValue value = JsonValue.Create("test", typeInfo, nodeOptions); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs index bf11f1cbf8b875..a00d4784f556c4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/DomTests.cs @@ -253,7 +253,7 @@ public static void SerializeToNode_WithEscaping() [Fact] public static void SerializeToDocument_WithJsonTypeInfo() { - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = MyPoco.Create(); using JsonDocument dom = JsonSerializer.SerializeToDocument(obj, typeInfo); @@ -279,7 +279,7 @@ public static void SerializeToDocument_WithJsonTypeInfo_NonGeneric() [Fact] public static void SerializeToElement_WithJsonTypeInfo() { - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = MyPoco.Create(); JsonElement element = JsonSerializer.SerializeToElement(obj, typeInfo); @@ -305,7 +305,7 @@ public static void SerializeToElement_WithJsonTypeInfo_NonGeneric() [Fact] public static void SerializeToNode_WithJsonTypeInfo() { - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = MyPoco.Create(); JsonNode node = JsonSerializer.SerializeToNode(obj, typeInfo); @@ -331,7 +331,7 @@ public static void DeserializeFromSpan_WithJsonTypeInfo() { ReadOnlySpan utf8Json = """{"StringProp":"Hello","IntArrayProp":[1,2]}"""u8; - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = JsonSerializer.Deserialize(utf8Json, typeInfo); obj.Verify(); @@ -381,7 +381,7 @@ public static void DeserializeFromSpan_NullValue() { ReadOnlySpan utf8Json = "null"u8; - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = JsonSerializer.Deserialize(utf8Json, typeInfo); Assert.Null(obj); @@ -392,7 +392,7 @@ public static void DeserializeFromJsonDocument_WithJsonTypeInfo() { using JsonDocument doc = JsonDocument.Parse(Json); - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = doc.Deserialize(typeInfo); obj.Verify(); @@ -416,7 +416,7 @@ public static void DeserializeFromJsonElement_WithJsonTypeInfo() using JsonDocument doc = JsonDocument.Parse(Json); JsonElement element = doc.RootElement; - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = element.Deserialize(typeInfo); obj.Verify(); @@ -440,7 +440,7 @@ public static void DeserializeFromJsonNode_WithJsonTypeInfo() { JsonNode node = JsonNode.Parse(Json); - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = node.Deserialize(typeInfo); obj.Verify(); @@ -463,7 +463,7 @@ public static void DeserializeFromCharSpan_WithJsonTypeInfo() { ReadOnlySpan jsonChars = Json.AsSpan(); - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = JsonSerializer.Deserialize(jsonChars, typeInfo); obj.Verify(); @@ -531,7 +531,7 @@ public static void DeserializeFromUtf8JsonReader_WithJsonTypeInfo() byte[] utf8Json = Encoding.UTF8.GetBytes(Json); Utf8JsonReader reader = new(utf8Json); - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = JsonSerializer.Deserialize(ref reader, typeInfo); obj.Verify(); @@ -555,7 +555,7 @@ public static void DeserializeFromStream_WithJsonTypeInfo() { using MemoryStream stream = new(Encoding.UTF8.GetBytes(Json)); - JsonTypeInfo typeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(MyPoco)); + JsonTypeInfo typeInfo = JsonSerializerOptions.Default.GetTypeInfo(); MyPoco obj = JsonSerializer.Deserialize(stream, typeInfo); obj.Verify(); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 7fed121a4f9c9b..e152d9e04a93be 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -277,9 +277,14 @@ + + + + + From bd9462c5150cd5465dda001cd458dedc4130e824 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 3 Jul 2026 09:09:25 +0300 Subject: [PATCH 2/4] Fix CI: gate LegacySchemaExporter_CanAccessReflectedMembers on IsReflectionEnabledByDefault The test calls JsonSerializerOptions.GetConverter on options derived from JsonSerializerOptions.Default, which requires the default reflection-based resolver. Enabling the trim/AOT analyzer bakes IsReflectionEnabledByDefault=false into the source-gen test project's runtimeconfig on all platforms (not just AOT), so the previous IsNotNativeAot gate let it run on CoreCLR where it threw NotSupportedException (EmptyJsonTypeInfoResolver). Gating on JsonSerializer.IsReflectionEnabledByDefault correctly skips it in the source-gen project while keeping it in the reflection project. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Text.Json/tests/Common/JsonSchemaExporterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs index f6771e365c66ba..190166ef19c3f8 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs @@ -219,7 +219,7 @@ public void JsonSchemaExporterOptions_Default_IsSame() Assert.Same(JsonSchemaExporterOptions.Default, JsonSchemaExporterOptions.Default); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [ConditionalFact(typeof(JsonSerializer), nameof(JsonSerializer.IsReflectionEnabledByDefault))] [RequiresUnreferencedCode("Uses private reflection to access System.Text.Json converter internals.")] [RequiresDynamicCode("Uses private reflection to access System.Text.Json converter internals.")] public void LegacySchemaExporter_CanAccessReflectedMembers() From 1d115bbb8a4368eeacdcae5a0cf8db3a7ba162b8 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 3 Jul 2026 12:47:51 +0300 Subject: [PATCH 3/4] Fix net481 source-gen polymorphic test failures Register the polymorphic derived types used by the CustomPolymorphismResolver tests (Dictionary/SortedDictionary/IReadOnlyDictionary value hierarchies and PolymorphicClass.DerivedAbstractClass) in PolymorphicTestsContext_Metadata so the source-gen resolver resolves them without reflection. This fixes the NotSupportedException seen on the net481 leg, where reflection is enabled but these tests route resolution entirely through the source-gen context. Also removes the now-redundant in-body 'if (!IsReflectionEnabledByDefault) return;' guards from the nearest-ancestor and dictionary-interface polymorphic tests: they are already gated via [ConditionalX(IsNotNativeAot)] and now pass under source-gen. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PolymorphicTests.CustomTypeHierarchies.cs | 50 +------------------ .../Serialization/PolymorphicTests.cs | 4 ++ 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs index b55213d9655669..0adcc0dfa86fe0 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs @@ -209,21 +209,12 @@ public async Task PolymorphicClass_CustomConfigWithBaseTypeFallback_InvalidTypeD [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization(PolymorphicClass.TestData testData) - { - // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return Task.CompletedTask; - } - - return TestMultiContextSerialization( + => TestMultiContextSerialization( testData.Value, testData.ExpectedJson, testData.ExpectedSerializationException, options: PolymorphicClass.CustomConfigWithNearestAncestorFallback, contexts: ~SerializedValueContext.BoxedValue); - } public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback().Select(entry => new object[] { entry }); @@ -231,21 +222,12 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearest [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization(PolymorphicClass.TestData testData) - { - // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return Task.CompletedTask; - } - - return TestMultiContextDeserialization( + => TestMultiContextDeserialization( testData.ExpectedJson, testData.ExpectedRoundtripValue, testData.ExpectedDeserializationException, equalityComparer: CreateJsonEqualityComparer(), options: PolymorphicClass.CustomConfigWithNearestAncestorFallback); - } public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() @@ -255,13 +237,6 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearest [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Serialization() { - // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return; - } - IEnumerable<(PolymorphicClass Value, string ExpectedJson)> inputs = PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() .Where(entry => entry.ExpectedSerializationException is null) @@ -273,13 +248,6 @@ public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestD [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Deserialization() { - // Nearest-ancestor fallback for class hierarchies resolves derived types via reflection; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return; - } - IEnumerable<(string ExpectedJson, PolymorphicClass ExpectedRoundtripValue)> inputs = PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback() .Where(entry => entry.ExpectedRoundtripValue is not null) @@ -1970,13 +1938,6 @@ public record TestData(PolymorphicDictionary Value, string ExpectedJson, Polymor [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicDictionaryInterface_Serialization() { - // Polymorphic dictionary-interface resolution uses the reflection-based metadata fallback; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return; - } - var values = new IEnumerable>[] { new List> { new KeyValuePair(0, 0) }, @@ -2001,13 +1962,6 @@ public async Task PolymorphicDictionaryInterface_Serialization() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] public async Task PolymorphicDictionaryInterface_Deserialization() { - // Polymorphic dictionary-interface resolution uses the reflection-based metadata fallback; - // skip in source-gen contexts where reflection is disabled. - if (!JsonSerializer.IsReflectionEnabledByDefault) - { - return; - } - string json = """ [ [ { "Key":0, "Value":0 } ], diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs index 21e63f95e7cd10..7b5915aa6ac00c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs @@ -245,6 +245,10 @@ public PolymorphicTests_Metadata() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Queue))] [JsonSerializable(typeof(ISet))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(SortedDictionary))] + [JsonSerializable(typeof(IReadOnlyDictionary))] + [JsonSerializable(typeof(PolymorphicClass.DerivedAbstractClass), TypeInfoPropertyName = "PolymorphicClass_DerivedAbstractClass")] [JsonSerializable(typeof(PolymorphicInterface.DerivedInterface1))] [JsonSerializable(typeof(PolymorphicInterface.DerivedInterface2))] internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext From 574812f5e77a926f6497509cc7fe70fec9dd2717 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 3 Jul 2026 13:20:21 +0300 Subject: [PATCH 4/4] Restore NativeAOT coverage for polymorphic custom-hierarchy tests The BaseTypeFallback / NearestAncestorFallback / DictionaryInterface tests were temporarily gated [ConditionalX(IsNotNativeAot)] while their derived types were unregistered in the source-gen context. Now that those types are registered, the tests resolve entirely through the source-gen resolver with no reflection, so they run under NativeAOT again -- matching main and preserving the outerloop coverage this PR exists to protect. Validated: NativeAOT run executes 524 of these cases with 0 failures (overall 10203 pass / 55 skip / 0 fail); net11.0 reflection-off 10503 pass / 0 fail; net481 + Roslyn3.11 build 0 warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PolymorphicTests.CustomTypeHierarchies.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs index 0adcc0dfa86fe0..c3d47893e33feb 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs @@ -129,7 +129,7 @@ public async Task PolymorphicClass_AllowOutOfOrderMetadata_RejectsInvalidInputs( //-- - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Serialization))] public Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Serialization(PolymorphicClass.TestData testData) => TestMultiContextSerialization( @@ -142,7 +142,7 @@ public Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Serializa public static IEnumerable Get_PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Serialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithBaseTypeFallback().Select(entry => new object[] { entry }); - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Deserialization))] public Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestData_Deserialization(PolymorphicClass.TestData testData) => TestMultiContextDeserialization( @@ -157,7 +157,7 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithBaseTyp .Where(entry => entry.ExpectedJson != null) .Select(entry => new object[] { entry }); - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestDataArray_Serialization() { IEnumerable<(PolymorphicClass Value, string ExpectedJson)> inputs = @@ -168,7 +168,7 @@ public async Task PolymorphicClass_CustomConfigWithBaseTypeFallback_TestDataArra await TestMultiContextSerialization(inputs, options: PolymorphicClass.CustomConfigWithBaseTypeFallback); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicClass_CustomConfigWithBaseTypeFallbacks_TestDataArray_Deserialization() { IEnumerable<(string ExpectedJson, PolymorphicClass ExpectedRoundtripValue)> inputs = @@ -182,7 +182,7 @@ await TestMultiContextDeserialization( options: PolymorphicClass.CustomConfigWithBaseTypeFallback); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [InlineData("$['$type']", """{ "$type" : "derivedClass1", "Number" : 42 }""")] [InlineData("$._case", """{ "_case" : "derivedClass1", "_case" : "derivedClass1", "Number" : 42 }""")] [InlineData("$._case", """{ "_case" : "derivedClass1", "Number" : 42, "_case" : "derivedClass1"}""")] @@ -206,7 +206,7 @@ public async Task PolymorphicClass_CustomConfigWithBaseTypeFallback_InvalidTypeD //--- - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization(PolymorphicClass.TestData testData) => TestMultiContextSerialization( @@ -219,7 +219,7 @@ public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Se public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Serialization() => PolymorphicClass.GetSerializeTestData_CustomConfigWithNearestAncestorFallback().Select(entry => new object[] { entry }); - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [MemberData(nameof(Get_PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization))] public Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestData_Deserialization(PolymorphicClass.TestData testData) => TestMultiContextDeserialization( @@ -234,7 +234,7 @@ public static IEnumerable Get_PolymorphicClass_CustomConfigWithNearest .Where(entry => entry.ExpectedJson != null) .Select(entry => new object[] { entry }); - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Serialization() { IEnumerable<(PolymorphicClass Value, string ExpectedJson)> inputs = @@ -245,7 +245,7 @@ public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestD await TestMultiContextSerialization(inputs, options: PolymorphicClass.CustomConfigWithNearestAncestorFallback); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicClass_CustomConfigWithNearestAncestorFallback_TestDataArray_Deserialization() { IEnumerable<(string ExpectedJson, PolymorphicClass ExpectedRoundtripValue)> inputs = @@ -259,7 +259,7 @@ await TestMultiContextDeserialization( options: PolymorphicClass.CustomConfigWithNearestAncestorFallback); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Theory] [InlineData("$['$type']", """{ "$type" : "derivedClass1", "Number" : 42 }""")] [InlineData("$._case", """{ "_case" : "derivedClass1", "_case" : "derivedClass1", "Number" : 42 }""")] [InlineData("$._case", """{ "_case" : "derivedClass1", "Number" : 42, "_case" : "derivedClass1"}""")] @@ -1935,7 +1935,7 @@ public static IEnumerable GetSerializeTestData() public record TestData(PolymorphicDictionary Value, string ExpectedJson, PolymorphicDictionary ExpectedRoundtripValue); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicDictionaryInterface_Serialization() { var values = new IEnumerable>[] @@ -1959,7 +1959,7 @@ public async Task PolymorphicDictionaryInterface_Serialization() JsonTestHelper.AssertJsonEqual(expectedJson, actualJson); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + [Fact] public async Task PolymorphicDictionaryInterface_Deserialization() { string json =