From 65885ec4e8efb71b67de999f4e86d75f6ee0297d Mon Sep 17 00:00:00 2001 From: eiriktsarpalis Date: Tue, 23 Jun 2026 18:30:55 +0300 Subject: [PATCH 1/6] Consolidate STJ polymorphism tests into shared abstract suite Moves PolymorphicTests.CustomTypeHierarchies.cs and PolymorphicTests.TypeClassifier.cs from tests/System.Text.Json.Tests/Serialization/ into tests/Common/ so they run under both reflection (11 wrappers) and source-gen (4 wrappers) via the SerializerWrapper pattern. Removes the duplicate PolymorphismTests.cs in the source-gen test project and wires the consolidated suite through a new tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs. Test-only refactoring in preparation for #129041. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../PolymorphicTests.CustomTypeHierarchies.cs | 43 +- .../PolymorphicTests.TypeClassifier.cs | 425 ++++++---- .../tests/Common/PolymorphicTests.cs | 720 +++++++++++++++++ .../tests/Common/SerializerTests.cs | 2 +- .../Serialization/PolymorphicTests.cs | 493 ++++++++++++ .../Serialization/PolymorphismTests.cs | 209 ----- ...m.Text.Json.SourceGeneration.Tests.targets | 5 +- .../Serialization/PolymorphicTests.cs | 735 +----------------- .../System.Text.Json.Tests.csproj | 5 +- 9 files changed, 1539 insertions(+), 1098 deletions(-) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/PolymorphicTests.CustomTypeHierarchies.cs (98%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/PolymorphicTests.TypeClassifier.cs (79%) create mode 100644 src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs delete mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphismTests.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs similarity index 98% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs rename to src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs index b9f948d193aae5..4769af65885343 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.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; @@ -1078,14 +1078,14 @@ public async Task NestedPolymorphicClassesIncreaseReadAndWriteStackWhenNeeded() [JsonDerivedType(typeof(TestNodeList), "NodeList")] [JsonDerivedType(typeof(TestLeaf), "Leaf")] - abstract class TestNode + public abstract class TestNode { public string Name { get; set; } public abstract void AssertEqualTo(TestNode other); } - class TestNodeList : TestNode + public class TestNodeList : TestNode { public string Info { get; set; } @@ -1118,7 +1118,7 @@ public override void AssertEqualTo(TestNode other) } } - class TestLeaf : TestNode + public class TestLeaf : TestNode { public string? Test { get; set; } @@ -2480,6 +2480,12 @@ public class PolymorphicClassWithoutDerivedTypeAttribute [Fact] public async Task PolymorphicClassWithNullDerivedTypeAttribute_ThrowsInvalidOperationException() { + // Generator NRE: registering this type triggers an unhandled NullReferenceException + // in JsonSourceGenerator.Parser.cs (the JsonDerivedType ctor arg is dereferenced + // without a null check). No SYSLIB diagnostic is emitted, so the SG path cannot + // reach the runtime InvalidOperationException. Validated under reflection only + // pending a generator fix. + if (Serializer.IsSourceGeneratedSerializer) return; var value = new PolymorphicClassWithNullDerivedTypeAttribute(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2659,6 +2665,8 @@ public class DerivedClass : PolymorphicGenericClass [Fact] public async Task PolymorphicDerivedGenericClass_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; PolymorphicDerivedGenericClass value = new PolymorphicDerivedGenericClass.DerivedClass(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2845,6 +2853,8 @@ public async Task OpenGenericDerivedType_DifferentTypeArguments_ProduceDifferent [Fact] public async Task OpenGenericDerivedType_NonGenericBase_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; var value = new NonGenericBaseWithOpenGenericDerived(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2858,6 +2868,8 @@ public class OpenDerived : NonGenericBaseWithOpenGenericDerived; [Fact] public async Task OpenGenericDerivedType_TypeArgsNotResolvable_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; // Derived : Base - T cannot be determined from Base var value = new OpenGenericBase_Unresolvable(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); @@ -2874,6 +2886,8 @@ public class OpenGenericDerived_Unresolvable : OpenGenericBase_Unresolvable : OpenGenericBase_GroundMismatch // registered on OpenGenericBase_GroundMismatch. // Position 0 (T) unifies with int, but position 1 (concrete int in derived's base @@ -3177,6 +3191,8 @@ public class OpenGenericDerived_Tuple : OpenGenericBase_Tuple<(T1, T2)> [Fact] public async Task OpenGenericDerivedType_AmbiguousInterfaceMatch_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; // Impl : IBase, IBase> registered on IBase>. // Both ancestors unify (T=List via the first interface, T=int via the second). // Result: ambiguous, throws. @@ -3192,6 +3208,8 @@ public class OpenGenericImpl_Ambiguous : IOpenGenericBase_Ambiguous, IOpen [Fact] public async Task OpenGenericDerivedType_UnboundParameter_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; // Derived : Base — T2 is unspeakable (not bound by the base type's args). var value = new OpenGenericBase_Unbound(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); @@ -3205,6 +3223,8 @@ public class OpenGenericDerived_Unbound : OpenGenericBase_Unbound; [Fact] public async Task OpenGenericDerivedType_ConstraintViolation_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; // Derived : Base where T : struct, registered on Base. // Constraint fails → InvalidOperationException. var value = new OpenGenericBase_StructConstraint(); @@ -3242,6 +3262,8 @@ public async Task OpenGenericDerivedType_DuplicateClosedAndOpenRegistration_Thro // Derived<> registration. The open form closes to Derived, producing a // duplicate derived-type registration. The existing dup-detection in // PolymorphicTypeResolver must surface this as InvalidOperationException. + // (Source generator accepts the configuration without warning; the runtime resolver + // catches the duplicate, so this scenario validates the same runtime path under both engines.) OpenGenericBase_DuplicateDerivedRegistrations value = new OpenGenericDerived_DuplicateDerivedRegistrations(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -3494,6 +3516,8 @@ public async Task Variance_BivariantInterface_BothViaVariance_DefaultThrows() [Fact] public async Task NestedGeneric_EnclosingMismatch_ThrowsInvalidOperationException() { + // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; // Pattern: NestedDerivedEnclosingMismatch : NestedBase.NestedBox>. // Target: NestedBase.NestedBox>. // The enclosing argument differs (int vs string) so unification MUST fail. The @@ -3788,9 +3812,10 @@ public bool Equals(TBaseType? left, TBaseType? right) public int GetHashCode(TBaseType _) => throw new NotImplementedException(); } - public class CustomPolymorphismResolver : DefaultJsonTypeInfoResolver + public class CustomPolymorphismResolver : IJsonTypeInfoResolver { - private List _jsonDerivedTypes = new(); + private readonly DefaultJsonTypeInfoResolver _inner = new(); + private readonly List _jsonDerivedTypes = new(); public CustomPolymorphismResolver(Type baseType) { @@ -3808,10 +3833,10 @@ public CustomPolymorphismResolver WithDerivedType(JsonDerivedType jsonDerivedTyp return this; } - public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) { - JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); - if (jsonTypeInfo.Type == BaseType) + JsonTypeInfo? jsonTypeInfo = _inner.GetTypeInfo(type, options); + if (jsonTypeInfo is not null && jsonTypeInfo.Type == BaseType) { jsonTypeInfo.PolymorphismOptions = new() { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.TypeClassifier.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs similarity index 79% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.TypeClassifier.cs rename to src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs index b8b7f26243f291..503e71325f33ae 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.TypeClassifier.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs @@ -33,6 +33,11 @@ public static JsonTypeClassifierContext Create( => (JsonTypeClassifierContext)s_ctor.Invoke([kind, declaringType, unionCases, derivedTypes, typeDiscriminatorPropertyName]); } + /// + /// Verifies polymorphic type resolution via and the + /// generation of . The same contract applies to both + /// the reflection and source-generated paths, so the suite is exercised by both wrappers. + /// public abstract partial class PolymorphicTests { #region Test Models @@ -435,6 +440,114 @@ public class PetOwner public ClassifiedAnimalBase? Pet { get; set; } } + // Hierarchy with a custom type-discriminator configuration, used to validate that + // PolymorphismOptions are surfaced identically under reflection and source generation. + [JsonPolymorphic( + TypeDiscriminatorPropertyName = "$kind", + IgnoreUnrecognizedTypeDiscriminators = true, + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] + [JsonDerivedType(typeof(StringDiscriminatorDerived), "string-derived")] + [JsonDerivedType(typeof(IntDiscriminatorDerived), 42)] + public class PolymorphicBaseWithCustomDiscriminator + { + public string? Value { get; set; } + } + + public sealed class StringDiscriminatorDerived : PolymorphicBaseWithCustomDiscriminator + { + public string? StringValue { get; set; } + } + + public sealed class IntDiscriminatorDerived : PolymorphicBaseWithCustomDiscriminator + { + public int IntValue { get; set; } + } + + // Hierarchy whose classifier is supplied through a strongly typed + // JsonTypeClassifierFactory referenced from the [JsonPolymorphic] attribute. + [JsonPolymorphic(TypeClassifier = typeof(FactoryClassifiedAnimalClassifierFactory))] + [JsonDerivedType(typeof(FactoryClassifiedDog), "dog")] + [JsonDerivedType(typeof(FactoryClassifiedCat), "cat")] + public class FactoryClassifiedAnimal + { + public string? Name { get; set; } + } + + public sealed class FactoryClassifiedDog : FactoryClassifiedAnimal + { + public string? Breed { get; set; } + } + + public sealed class FactoryClassifiedCat : FactoryClassifiedAnimal + { + public int Lives { get; set; } + } + + public sealed class FactoryClassifiedAnimalClassifierFactory : JsonTypeClassifierFactory + { + public override JsonTypeClassifier CreateJsonClassifier(JsonTypeClassifierContext context, JsonSerializerOptions options) + { + Assert.Equal(JsonTypeClassifierKind.PolymorphicType, context.Kind); + Assert.Equal(typeof(FactoryClassifiedAnimal), context.DeclaringType); + Assert.Equal("$type", context.TypeDiscriminatorPropertyName); + Assert.Collection( + context.DerivedTypes, + derivedType => + { + Assert.Equal(typeof(FactoryClassifiedDog), derivedType.DerivedType); + Assert.Equal("dog", derivedType.TypeDiscriminator); + }, + derivedType => + { + Assert.Equal(typeof(FactoryClassifiedCat), derivedType.DerivedType); + Assert.Equal("cat", derivedType.TypeDiscriminator); + }); + + return static (ref Utf8JsonReader reader) => + { + if (reader.TokenType != JsonTokenType.StartObject) + return null; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + if (reader.ValueTextEquals("Breed"u8)) return typeof(FactoryClassifiedDog); + if (reader.ValueTextEquals("Lives"u8)) return typeof(FactoryClassifiedCat); + reader.Read(); + reader.TrySkip(); + } + } + + return null; + }; + } + } + + // Collection type carrying polymorphism options. + [JsonPolymorphic(TypeDiscriminatorPropertyName = "$kind")] + [JsonDerivedType(typeof(PolymorphicIntListDerived), "derived-list")] + public class PolymorphicIntList : List + { + } + + public sealed class PolymorphicIntListDerived : PolymorphicIntList + { + } + + // Open generic hierarchy where the derived type only partially unifies with the base. + [JsonDerivedType(typeof(OpenGenericDerived<>), "derived")] + public class OpenGenericBase + { + public T1? Value1 { get; set; } + public T2? Value2 { get; set; } + } + + public sealed class OpenGenericDerived : OpenGenericBase + { + public T? Extra { get; set; } + } + #endregion #region Classifier via contract customization — structural matching @@ -594,12 +707,8 @@ public async Task Classifier_IntDiscriminator_ResolvesCorrectType(string json, T [Fact] public async Task OptionsTypeClassifier_UsesPolymorphicContextToSelectFactory() { - var options = new JsonSerializerOptions - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver() - }; - - options.TypeClassifiers.Add(new TestStructuralClassifierFactory()); + JsonSerializerOptions options = Serializer.CreateOptions( + configure: o => o.TypeClassifiers.Add(new TestStructuralClassifierFactory())); string json = """{"Name":"Rex","Breed":"Labrador"}"""; ClassifiedAnimalBase? result = await Serializer.DeserializeWrapper(json, options); @@ -612,24 +721,15 @@ public async Task OptionsTypeClassifier_UsesPolymorphicContextToSelectFactory() public void OptionsTypeClassifier_IsVisibleToPolymorphicModifier() { JsonTypeClassifier? classifierFromModifier = null; - var options = new JsonSerializerOptions - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + JsonSerializerOptions options = Serializer.CreateOptions( + configure: o => o.TypeClassifiers.Add(new TestStructuralClassifierFactory()), + modifier: typeInfo => { - Modifiers = + if (typeInfo.Type == typeof(ClassifiedAnimalBase)) { - typeInfo => - { - if (typeInfo.Type == typeof(ClassifiedAnimalBase)) - { - classifierFromModifier = typeInfo.TypeClassifier; - } - } + classifierFromModifier = typeInfo.TypeClassifier; } - } - }; - - options.TypeClassifiers.Add(new TestStructuralClassifierFactory()); + }); JsonTypeInfo typeInfo = options.GetTypeInfo(typeof(ClassifiedAnimalBase)); @@ -700,22 +800,13 @@ public void Classifier_ViaAttribute_IsVisibleToModifier() { JsonTypeClassifier? classifierFromModifier = null; - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(AttrClassifiedAnimal)) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(AttrClassifiedAnimal)) - { - classifierFromModifier = typeInfo.TypeClassifier; - } - } - } + classifierFromModifier = typeInfo.TypeClassifier; } - }; + }); JsonTypeInfo typeInfo = options.GetTypeInfo(typeof(AttrClassifiedAnimal)); @@ -1026,23 +1117,14 @@ public async Task Classifier_PlusAllowOutOfOrder_ClassifierWins() "kind"); JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - AllowOutOfOrderMetadataProperties = true, - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(ClassifiedAnimalBase)) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(ClassifiedAnimalBase)) - { - typeInfo.TypeClassifier = classify; - } - } - } + typeInfo.TypeClassifier = classify; } - }; + }); + options.AllowOutOfOrderMetadataProperties = true; // "kind" property is not standard $type — classifier handles it string json = """{"Name":"Rex","kind":"dog","Breed":"Lab"}"""; @@ -1110,40 +1192,31 @@ await Assert.ThrowsAsync( [Fact] public async Task Classifier_WithCamelCaseNamingPolicy_ReadsRawJsonPropertyNames() { - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(ClassifiedAnimalBase)) { - Modifiers = + // Classifier reads raw JSON property names, not C# names + typeInfo.TypeClassifier = (ref Utf8JsonReader reader) => { - typeInfo => + if (reader.TokenType != JsonTokenType.StartObject) return null; + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - if (typeInfo.Type == typeof(ClassifiedAnimalBase)) + if (reader.TokenType == JsonTokenType.PropertyName) { - // Classifier reads raw JSON property names, not C# names - typeInfo.TypeClassifier = (ref Utf8JsonReader reader) => - { - if (reader.TokenType != JsonTokenType.StartObject) return null; - while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) - { - if (reader.TokenType == JsonTokenType.PropertyName) - { - // JSON uses camelCase "breed", not PascalCase "Breed" - if (reader.ValueTextEquals("breed"u8)) return typeof(ClassifiedDog); - if (reader.ValueTextEquals("lives"u8)) return typeof(ClassifiedCat); - reader.Read(); - reader.TrySkip(); - } - } - - return null; - }; + // JSON uses camelCase "breed", not PascalCase "Breed" + if (reader.ValueTextEquals("breed"u8)) return typeof(ClassifiedDog); + if (reader.ValueTextEquals("lives"u8)) return typeof(ClassifiedCat); + reader.Read(); + reader.TrySkip(); } } - } + + return null; + }; } - }; + }); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; string json = """{"name":"Rex","breed":"Lab"}"""; ClassifiedAnimalBase? result = await Serializer.DeserializeWrapper(json, options); @@ -1168,41 +1241,32 @@ public async Task Classifier_DifferentClassifiersForDifferentBaseTypes() "kind"); JsonTypeClassifier animalClassify = animalFactory.CreateJsonClassifier(animalContext, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(ClassifiedAnimalBase)) + { + typeInfo.TypeClassifier = animalClassify; + } + else if (typeInfo.Type == typeof(ClassifiedShape)) { - Modifiers = + typeInfo.TypeClassifier = (ref Utf8JsonReader reader) => { - typeInfo => + if (reader.TokenType != JsonTokenType.StartObject) return null; + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - if (typeInfo.Type == typeof(ClassifiedAnimalBase)) + if (reader.TokenType == JsonTokenType.PropertyName) { - typeInfo.TypeClassifier = animalClassify; - } - else if (typeInfo.Type == typeof(ClassifiedShape)) - { - typeInfo.TypeClassifier = (ref Utf8JsonReader reader) => - { - if (reader.TokenType != JsonTokenType.StartObject) return null; - while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) - { - if (reader.TokenType == JsonTokenType.PropertyName) - { - if (reader.ValueTextEquals("Radius"u8)) return typeof(ClassifiedCircle); - if (reader.ValueTextEquals("Width"u8)) return typeof(ClassifiedRectangle); - reader.Read(); - reader.TrySkip(); - } - } - - return null; - }; + if (reader.ValueTextEquals("Radius"u8)) return typeof(ClassifiedCircle); + if (reader.ValueTextEquals("Width"u8)) return typeof(ClassifiedRectangle); + reader.Read(); + reader.TrySkip(); } } - } + + return null; + }; } - }; + }); // Animal string animalJson = """{"kind":"dog","Name":"Rex","Breed":"Lab"}"""; @@ -1285,35 +1349,110 @@ public async Task Classifier_EmptyObject_MatchesFirstDeclaredCase() #endregion + #region Generation parity (folded from source-gen suite) + + [Fact] + public void PolymorphismOptions_AreGenerated() + { + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(); + JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); + + Assert.True(options.IgnoreUnrecognizedTypeDiscriminators); + Assert.Equal(JsonUnknownDerivedTypeHandling.FallBackToBaseType, options.UnknownDerivedTypeHandling); + Assert.Equal("$kind", options.TypeDiscriminatorPropertyName); + Assert.Collection( + options.DerivedTypes, + derivedType => + { + Assert.Equal(typeof(StringDiscriminatorDerived), derivedType.DerivedType); + Assert.Equal("string-derived", derivedType.TypeDiscriminator); + }, + derivedType => + { + Assert.Equal(typeof(IntDiscriminatorDerived), derivedType.DerivedType); + Assert.Equal(42, derivedType.TypeDiscriminator); + }); + } + + [Fact] + public void CollectionPolymorphismOptions_AreGenerated() + { + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(); + JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); + + Assert.Equal("$kind", options.TypeDiscriminatorPropertyName); + JsonDerivedType derivedType = Assert.Single(options.DerivedTypes); + Assert.Equal(typeof(PolymorphicIntListDerived), derivedType.DerivedType); + Assert.Equal("derived-list", derivedType.TypeDiscriminator); + } + + [Fact] + public async Task PolymorphicTypeClassifier_IsGeneratedAndVisibleToModifier() + { + bool modifierObservedClassifier = false; + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => + { + if (typeInfo.Type == typeof(FactoryClassifiedAnimal)) + { + Assert.NotNull(typeInfo.PolymorphismOptions); + Assert.NotNull(typeInfo.TypeClassifier); + modifierObservedClassifier = true; + } + }); + + FactoryClassifiedAnimal? result = await Serializer.DeserializeWrapper( + """{"Name":"Rex","Breed":"Labrador"}""", + options); + + FactoryClassifiedDog dog = Assert.IsType(result); + Assert.Equal("Rex", dog.Name); + Assert.Equal("Labrador", dog.Breed); + Assert.True(modifierObservedClassifier); + } + + [Fact] + public async Task OpenGenericDerivedType_PartiallyConcrete_RoundTrips() + { + // OpenGenericDerived : OpenGenericBase registered on OpenGenericBase. + // Position 0 (T) unifies to string; position 1 (concrete int) matches. The resolved derived + // type for the closed base must be OpenGenericDerived. + JsonTypeInfo> typeInfo = Serializer.GetTypeInfo>(); + JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); + JsonDerivedType derivedType = Assert.Single(options.DerivedTypes); + Assert.Equal(typeof(OpenGenericDerived), derivedType.DerivedType); + Assert.Equal("derived", derivedType.TypeDiscriminator); + + OpenGenericBase value = new OpenGenericDerived { Extra = "hello" }; + string json = await Serializer.SerializeWrapper(value, typeInfo); + Assert.Contains("\"$type\":\"derived\"", json); + + OpenGenericBase? result = await Serializer.DeserializeWrapper(json, typeInfo); + OpenGenericDerived d = Assert.IsType>(result); + Assert.Equal("hello", d.Extra); + } + + #endregion + #region Helpers - private static JsonSerializerOptions CreateOptionsWithStructuralClassifier() + private JsonSerializerOptions CreateOptionsWithStructuralClassifier() where TBase : class { - return new JsonSerializerOptions + return Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(TBase) && typeInfo.PolymorphismOptions is not null) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(TBase) && typeInfo.PolymorphismOptions is not null) - { - var factory = new TestStructuralClassifierFactory(); - var context = JsonTypeClassifierContextTestExtensions.Create( - typeof(TBase), - Array.Empty(), - typeInfo.PolymorphismOptions.DerivedTypes.ToList(), - typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName); - - typeInfo.TypeClassifier = - factory.CreateJsonClassifier(context, typeInfo.Options); - } - } - } + var factory = new TestStructuralClassifierFactory(); + var context = JsonTypeClassifierContextTestExtensions.Create( + typeof(TBase), + Array.Empty(), + typeInfo.PolymorphismOptions.DerivedTypes.ToList(), + typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName); + + typeInfo.TypeClassifier = + factory.CreateJsonClassifier(context, typeInfo.Options); } - }; + }); } [Fact] @@ -1329,24 +1468,15 @@ public async Task Classifier_WithReferenceHandlerPreserve_PreservesReferences() "kind"); JsonTypeClassifier classify = factory.CreateJsonClassifier(context, new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); - var options = new JsonSerializerOptions + JsonSerializerOptions options = Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - ReferenceHandler = ReferenceHandler.Preserve, - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(ClassifiedAnimalBase) && typeInfo.PolymorphismOptions is not null) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(ClassifiedAnimalBase) && typeInfo.PolymorphismOptions is not null) - { - typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = "kind"; - typeInfo.TypeClassifier = classify; - } - } - } + typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = "kind"; + typeInfo.TypeClassifier = classify; } - }; + }); + options.ReferenceHandler = ReferenceHandler.Preserve; var dog = new ClassifiedDog { Name = "Rex", Breed = "Lab" }; ClassifiedAnimalBase[] payload = new ClassifiedAnimalBase[] { dog, dog }; @@ -1363,25 +1493,16 @@ public async Task Classifier_WithReferenceHandlerPreserve_PreservesReferences() Assert.Equal("Lab", ((ClassifiedDog)result[0]).Breed); } - private static JsonSerializerOptions CreateOptionsWithClassifier(JsonTypeClassifier classifier) + private JsonSerializerOptions CreateOptionsWithClassifier(JsonTypeClassifier classifier) where TBase : class { - return new JsonSerializerOptions + return Serializer.GetDefaultOptionsWithMetadataModifier(typeInfo => { - TypeInfoResolver = new DefaultJsonTypeInfoResolver + if (typeInfo.Type == typeof(TBase) && typeInfo.PolymorphismOptions is not null) { - Modifiers = - { - typeInfo => - { - if (typeInfo.Type == typeof(TBase) && typeInfo.PolymorphismOptions is not null) - { - typeInfo.TypeClassifier = classifier; - } - } - } + typeInfo.TypeClassifier = classifier; } - }; + }); } #endregion diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs new file mode 100644 index 00000000000000..9e81eea1d48638 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs @@ -0,0 +1,720 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public abstract partial class PolymorphicTests : SerializerTests + { + public PolymorphicTests(JsonSerializerWrapper serializer) : base(serializer) + { + } + + [Theory] + [InlineData(1, "1")] + [InlineData("stringValue", """ + "stringValue" + """)] + [InlineData(true, "true")] + [InlineData(null, "null")] + [InlineData(new int[] { 1, 2, 3}, "[1,2,3]")] + public async Task PrimitivesAsRootObject(object? value, string expectedJson) + { + string json = await Serializer.SerializeWrapper(value); + Assert.Equal(expectedJson, json); + json = await Serializer.SerializeWrapper(value, typeof(object)); + Assert.Equal(expectedJson, json); + + var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; + JsonTypeInfo objectTypeInfo = options.GetTypeInfo(); + json = await Serializer.SerializeWrapper(value, objectTypeInfo); + Assert.Equal(expectedJson, json); + } + + [Fact] + public void ReadPrimitivesFail() + { + Assert.Throws(() => JsonSerializer.Deserialize(@"")); + Assert.Throws(() => JsonSerializer.Deserialize(@"a")); + } + + [Fact] + public void ParseUntyped() + { + object obj = JsonSerializer.Deserialize(""" + "hello" + """); + Assert.IsType(obj); + JsonElement element = (JsonElement)obj; + Assert.Equal(JsonValueKind.String, element.ValueKind); + Assert.Equal("hello", element.GetString()); + + obj = JsonSerializer.Deserialize(@"true"); + element = (JsonElement)obj; + Assert.Equal(JsonValueKind.True, element.ValueKind); + Assert.True(element.GetBoolean()); + + obj = JsonSerializer.Deserialize(@"null"); + Assert.Null(obj); + + obj = JsonSerializer.Deserialize("[]"); + element = (JsonElement)obj; + Assert.Equal(JsonValueKind.Array, element.ValueKind); + } + + [Fact] + public async Task ArrayAsRootObject() + { + const string ExpectedJson = """[1,true,{"City":"MyCity"},null,"foo"]"""; + const string ReversedExpectedJson = """["foo",null,{"City":"MyCity"},true,1]"""; + + string[] expectedObjects = { """ + "foo" + """, @"null", """{"City":"MyCity"}""", @"true", @"1" }; + + var address = new Address(); + address.Initialize(); + + var array = new object[] { 1, true, address, null, "foo" }; + string json = await Serializer.SerializeWrapper(array); + Assert.Equal(ExpectedJson, json); + + var dictionary = new Dictionary { { "City", "MyCity" } }; + var arrayWithDictionary = new object[] { 1, true, dictionary, null, "foo" }; + json = await Serializer.SerializeWrapper(arrayWithDictionary); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(array); + Assert.Equal(ExpectedJson, json); + + List list = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(list); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(list); + Assert.Equal(ExpectedJson, json); + + IEnumerable ienumerable = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(ienumerable); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(ienumerable); + Assert.Equal(ExpectedJson, json); + + IList ilist = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(ilist); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(ilist); + Assert.Equal(ExpectedJson, json); + + ICollection icollection = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(icollection); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(icollection); + Assert.Equal(ExpectedJson, json); + + IEnumerable genericIEnumerable = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(genericIEnumerable); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(genericIEnumerable); + Assert.Equal(ExpectedJson, json); + + IList genericIList = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(genericIList); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(genericIList); + Assert.Equal(ExpectedJson, json); + + ICollection genericICollection = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(genericICollection); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(genericICollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyCollection genericIReadOnlyCollection = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(genericIReadOnlyCollection); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(genericIReadOnlyCollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyList genericIReadonlyList = new List { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(genericIReadonlyList); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(genericIReadonlyList); + Assert.Equal(ExpectedJson, json); + + ISet iset = new HashSet { 1, true, address, null, "foo" }; + json = await Serializer.SerializeWrapper(iset); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(iset); + Assert.Equal(ExpectedJson, json); + + Stack stack = new Stack(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(stack); + Assert.Equal(ReversedExpectedJson, json); + + json = await Serializer.SerializeWrapper(stack); + Assert.Equal(ReversedExpectedJson, json); + + Queue queue = new Queue(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(queue); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(queue); + Assert.Equal(ExpectedJson, json); + + HashSet hashset = new HashSet(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(hashset); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(hashset); + Assert.Equal(ExpectedJson, json); + + LinkedList linkedlist = new LinkedList(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(linkedlist); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(linkedlist); + Assert.Equal(ExpectedJson, json); + + ImmutableArray immutablearray = ImmutableArray.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(immutablearray); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(immutablearray); + Assert.Equal(ExpectedJson, json); + + IImmutableList iimmutablelist = ImmutableList.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(iimmutablelist); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(iimmutablelist); + Assert.Equal(ExpectedJson, json); + + IImmutableStack iimmutablestack = ImmutableStack.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(iimmutablestack); + Assert.Equal(ReversedExpectedJson, json); + + json = await Serializer.SerializeWrapper(iimmutablestack); + Assert.Equal(ReversedExpectedJson, json); + + IImmutableQueue iimmutablequeue = ImmutableQueue.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(iimmutablequeue); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(iimmutablequeue); + Assert.Equal(ExpectedJson, json); + + IImmutableSet iimmutableset = ImmutableHashSet.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(iimmutableset); + foreach (string obj in expectedObjects) + { + Assert.Contains(obj, json); + } + + json = await Serializer.SerializeWrapper(iimmutableset); + foreach (string obj in expectedObjects) + { + Assert.Contains(obj, json); + } + + ImmutableHashSet immutablehashset = ImmutableHashSet.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(immutablehashset); + foreach (string obj in expectedObjects) + { + Assert.Contains(obj, json); + } + + json = await Serializer.SerializeWrapper(immutablehashset); + foreach (string obj in expectedObjects) + { + Assert.Contains(obj, json); + } + + ImmutableList immutablelist = ImmutableList.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(immutablelist); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(immutablelist); + Assert.Equal(ExpectedJson, json); + + ImmutableStack immutablestack = ImmutableStack.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(immutablestack); + Assert.Equal(ReversedExpectedJson, json); + + json = await Serializer.SerializeWrapper(immutablestack); + Assert.Equal(ReversedExpectedJson, json); + + ImmutableQueue immutablequeue = ImmutableQueue.CreateRange(new List { 1, true, address, null, "foo" }); + json = await Serializer.SerializeWrapper(immutablequeue); + Assert.Equal(ExpectedJson, json); + + json = await Serializer.SerializeWrapper(immutablequeue); + Assert.Equal(ExpectedJson, json); + } + + [Fact] + public async Task SimpleTestClassAsRootObject() + { + // Sanity checks on test type. + Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyInt16").PropertyType); + Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyBooleanTrue").PropertyType); + Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyInt16Array").PropertyType); + + var obj = new SimpleTestClassWithObject(); + obj.Initialize(); + + // Verify with actual type. + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(""" + "MyInt16":1 + """, json); + Assert.Contains(""" + "MyBooleanTrue":true + """, json); + Assert.Contains(""" + "MyInt16Array":[1] + """, json); + + // Verify with object type. + json = await Serializer.SerializeWrapper(obj); + Assert.Contains(""" + "MyInt16":1 + """, json); + Assert.Contains(""" + "MyBooleanTrue":true + """, json); + Assert.Contains(""" + "MyInt16Array":[1] + """, json); + } + + [Fact] + public async Task NestedObjectAsRootObject() + { + void Verify(string json) + { + Assert.Contains(""" + "Address":{"City":"MyCity"} + """, json); + Assert.Contains(""" + "List":["Hello","World"] + """, json); + Assert.Contains(""" + "Array":["Hello","Again"] + """, json); + Assert.Contains(""" + "IEnumerable":["Hello","World"] + """, json); + Assert.Contains(""" + "IList":["Hello","World"] + """, json); + Assert.Contains(""" + "ICollection":["Hello","World"] + """, json); + Assert.Contains(""" + "IEnumerableT":["Hello","World"] + """, json); + Assert.Contains(""" + "IListT":["Hello","World"] + """, json); + Assert.Contains(""" + "ICollectionT":["Hello","World"] + """, json); + Assert.Contains(""" + "IReadOnlyCollectionT":["Hello","World"] + """, json); + Assert.Contains(""" + "IReadOnlyListT":["Hello","World"] + """, json); + Assert.Contains(""" + "ISetT":["Hello","World"] + """, json); + Assert.Contains(""" + "IReadOnlySetT":["Hello","World"] + """, json); + Assert.Contains(""" + "StackT":["World","Hello"] + """, json); + Assert.Contains(""" + "QueueT":["Hello","World"] + """, json); + Assert.Contains(""" + "HashSetT":["Hello","World"] + """, json); + Assert.Contains(""" + "LinkedListT":["Hello","World"] + """, json); + Assert.Contains(""" + "SortedSetT":["Hello","World"] + """, json); + Assert.Contains(""" + "ImmutableArrayT":["Hello","World"] + """, json); + Assert.Contains(""" + "IImmutableListT":["Hello","World"] + """, json); + Assert.Contains(""" + "IImmutableStackT":["World","Hello"] + """, json); + Assert.Contains(""" + "IImmutableQueueT":["Hello","World"] + """, json); + Assert.True(json.Contains(""" + "IImmutableSetT":["Hello","World"] + """) || json.Contains(""" + "IImmutableSetT":["World","Hello"] + """)); + Assert.True(json.Contains(""" + "ImmutableHashSetT":["Hello","World"] + """) || json.Contains(""" + "ImmutableHashSetT":["World","Hello"] + """)); + Assert.Contains(""" + "ImmutableListT":["Hello","World"] + """, json); + Assert.Contains(""" + "ImmutableStackT":["World","Hello"] + """, json); + Assert.Contains(""" + "ImmutableQueueT":["Hello","World"] + """, json); + Assert.Contains(""" + "ImmutableSortedSetT":["Hello","World"] + """, json); + Assert.Contains(""" + "NullableInt":42 + """, json); + Assert.Contains(""" + "Object":{} + """, json); + Assert.Contains(""" + "NullableIntArray":[null,42,null] + """, json); + } + + // Sanity checks on test type. + Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("Address").PropertyType); + Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("List").PropertyType); + Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("Array").PropertyType); + Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("NullableInt").PropertyType); + Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("NullableIntArray").PropertyType); + + var obj = new ObjectWithObjectProperties(); + + string json = await Serializer.SerializeWrapper(obj); + Verify(json); + + json = await Serializer.SerializeWrapper(obj); + Verify(json); + } + + [Fact] + public async Task NestedObjectAsRootObjectIgnoreNullable() + { + // Ensure that null properties are properly written and support ignore. + var obj = new ObjectWithObjectProperties(); + obj.NullableInt = null; + + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(""" + "NullableInt":null + """, json); + + JsonSerializerOptions options = new JsonSerializerOptions(); + options.IgnoreNullValues = true; + json = await Serializer.SerializeWrapper(obj, options); + Assert.DoesNotContain(""" + "NullableInt":null + """, json); + } + + [Fact] + public async Task StaticAnalysisBaseline() + { + Customer customer = new Customer(); + customer.Initialize(); + customer.Verify(); + + string json = await Serializer.SerializeWrapper(customer); + Customer deserializedCustomer = JsonSerializer.Deserialize(json); + deserializedCustomer.Verify(); + } + + [Fact] + public async Task StaticAnalysis() + { + Customer customer = new Customer(); + customer.Initialize(); + customer.Verify(); + + Person person = customer; + + // Generic inference used = + string json = await Serializer.SerializeWrapper(person); + + Customer deserializedCustomer = JsonSerializer.Deserialize(json); + + // We only serialized the Person base class, so the Customer fields should be default. + Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); + Assert.Equal(0, deserializedCustomer.CreditLimit); + ((Person)deserializedCustomer).VerifyNonVirtual(); + } + + [Fact] + public async Task WriteStringWithRuntimeType() + { + Customer customer = new Customer(); + customer.Initialize(); + customer.Verify(); + + Person person = customer; + + string json = await Serializer.SerializeWrapper(person, person.GetType()); + + Customer deserializedCustomer = JsonSerializer.Deserialize(json); + + // We serialized the Customer + Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); + deserializedCustomer.Verify(); + } + + [Fact] + public async Task StaticAnalysisWithRelationship() + { + UsaCustomer usaCustomer = new UsaCustomer(); + usaCustomer.Initialize(); + usaCustomer.Verify(); + + // Note: this could be typeof(UsaAddress) if we preserve objects created in the ctor. Currently we only preserve IEnumerables. + Assert.Equal(typeof(Address), usaCustomer.Address.GetType()); + + Customer customer = usaCustomer; + + // Generic inference used = + string json = await Serializer.SerializeWrapper(customer); + + UsaCustomer deserializedCustomer = JsonSerializer.Deserialize(json); + + // We only serialized the Customer base class + Assert.Equal(typeof(UsaCustomer), deserializedCustomer.GetType()); + Assert.Equal(typeof(Address), deserializedCustomer.Address.GetType()); + ((Customer)deserializedCustomer).VerifyNonVirtual(); + } + + [Fact] + public void PolymorphicInterface_NotSupported() + { + Assert.Throws(() => JsonSerializer.Deserialize("""{ "Value": "A value", "Thing": { "Number": 123 } }""")); + } + + [Fact] + public void GenericListOfInterface_WithInvalidJson_ThrowsJsonException() + { + Assert.Throws(() => JsonSerializer.Deserialize("false")); + Assert.Throws(() => JsonSerializer.Deserialize("{}")); + } + + [Fact] + public void GenericListOfInterface_WithValidJson_ThrowsNotSupportedException() + { + Assert.Throws(() => JsonSerializer.Deserialize("[{}]")); + } + + [Fact] + public void GenericDictionaryOfInterface_WithInvalidJson_ThrowsJsonException() + { + Assert.Throws(() => JsonSerializer.Deserialize("""{"":1}""")); + } + + [Fact] + public void GenericDictionaryOfInterface_WithValidJson_ThrowsNotSupportedException() + { + Assert.Throws(() => JsonSerializer.Deserialize("""{"":{}}""")); + } + + [Fact] + public async Task AnonymousType() + { + // Anonymous types are unspeakable and cannot be registered with the source generator; validated under reflection only. + if (Serializer.IsSourceGeneratedSerializer) return; + const string Expected = """{"x":1,"y":true}"""; + var value = new { x = 1, y = true }; + + // Strongly-typed. + string json = await Serializer.SerializeWrapper(value); + Assert.Equal(Expected, json); + + // Boxed. + object objValue = value; + json = await Serializer.SerializeWrapper(objValue); + Assert.Equal(Expected, json); + } + + [Fact] + public async Task CustomResolverWithFailingAncestorType_DoesNotSurfaceException() + { + var options = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + static typeInfo => + { + if (typeInfo.Type == typeof(MyThing) || + typeInfo.Type == typeof(IList)) + { + throw new InvalidOperationException("some latent custom resolution bug"); + } + } + } + } + }; + + object value = new MyDerivedThing { Number = 42 }; + string json = await Serializer.SerializeWrapper(value, options); + Assert.Equal("""{"Number":42}""", json); + + value = new int[] { 1, 2, 3 }; + json = await Serializer.SerializeWrapper(value, options); + Assert.Equal("[1,2,3]", json); + } + + class MyClass + { + public string Value { get; set; } + public IThing Thing { get; set; } + } + + interface IThing + { + int Number { get; set; } + } + + class MyThing : IThing + { + public int Number { get; set; } + } + + class MyDerivedThing : MyThing + { + } + + class MyThingCollection : List { } + + 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) + { + InvalidOperationException ex; + object value = Activator.CreateInstance(derivedType); + + ex = Assert.Throws(() => Serializer.GetTypeInfo(baseType)); + ValidateException(ex); + + ex = await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value, baseType)); + ValidateException(ex); + + ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", baseType)); + ValidateException(ex); + + void ValidateException(InvalidOperationException ex) + { + Assert.Contains($"The type '{derivedType}' contains property 'Type' that conflicts with an existing metadata property name.", ex.Message); + } + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] + [JsonDerivedType(typeof(Derived), nameof(Derived))] + public abstract class PolymorphicTypeWithConflictingPropertyNameAtBase + { + public string Type { get; set; } + + public class Derived : PolymorphicTypeWithConflictingPropertyNameAtBase + { + public string Name { get; set; } + } + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] + [JsonDerivedType(typeof(Derived), nameof(Derived))] + public abstract class PolymorphicTypeWithConflictingPropertyNameAtDerived + { + public class Derived : PolymorphicTypeWithConflictingPropertyNameAtDerived + { + public string Type { get; set; } + } + } + + [Fact] + public async Task PolymorphicTypeWithIgnoredConflictingPropertyName_Supported() + { + PolymorphicTypeWithIgnoredConflictingPropertyName value = new PolymorphicTypeWithIgnoredConflictingPropertyName.Derived(); + + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName)); + Assert.NotNull(typeInfo); + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"Type":"Derived"}""", json); + + value = await Serializer.DeserializeWrapper(json); + Assert.IsType(value); + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] + [JsonDerivedType(typeof(Derived), nameof(Derived))] + public abstract class PolymorphicTypeWithIgnoredConflictingPropertyName + { + [JsonIgnore] + public string Type { get; set; } + + public class Derived : PolymorphicTypeWithIgnoredConflictingPropertyName; + } + + [Fact] + public async Task PolymorphicTypeWithExtensionDataConflictingPropertyName_Supported() + { + PolymorphicTypeWithExtensionDataConflictingPropertyName value = new PolymorphicTypeWithExtensionDataConflictingPropertyName.Derived(); + + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName)); + Assert.NotNull(typeInfo); + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"Type":"Derived"}""", json); + + value = await Serializer.DeserializeWrapper("""{"Type":"Derived","extraProp":null}"""); + Assert.IsType(value); + Assert.Equal(1, value.Type.Count); + Assert.Contains("extraProp", value.Type); + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] + [JsonDerivedType(typeof(Derived), nameof(Derived))] + public abstract class PolymorphicTypeWithExtensionDataConflictingPropertyName + { + [JsonExtensionData] + public JsonObject Type { get; set; } + + public class Derived : PolymorphicTypeWithExtensionDataConflictingPropertyName; + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs index c99336a0dce707..59b8541318b57f 100644 --- a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs @@ -357,7 +357,7 @@ protected async Task TestMultiContextDeserialization( await TestMultiContextDeserialization>(json, expectedValues, expectedExceptionType: null, contexts, options, listEqualityComparer); } - private class GenericPoco + internal class GenericPoco { public T Property { get; set; } } 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 new file mode 100644 index 00000000000000..118bb2421ec48b --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphicTests.cs @@ -0,0 +1,493 @@ +// 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.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Tests; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public sealed partial class PolymorphicTests_Metadata : PolymorphicTests + { + public PolymorphicTests_Metadata() + : base(new StringSerializerWrapper(PolymorphicTestsContext_Metadata.Default)) + { + } + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(AttrClassifiedAnimal))] + [JsonSerializable(typeof(BinaryTree))] + [JsonSerializable(typeof(ClassifiedAnimalBase))] + [JsonSerializable(typeof(ClassifiedAnimalBase[]))] + [JsonSerializable(typeof(ClassifiedAnimalDeepHierarchy))] + [JsonSerializable(typeof(ClassifiedShape))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(DrawingCanvas))] + [JsonSerializable(typeof(FactoryClassifiedAnimal))] + [JsonSerializable(typeof(IEnumerable[]))] + [JsonSerializable(typeof(IEnumerable>[]))] + [JsonSerializable(typeof(IOpenGenericBase))] + [JsonSerializable(typeof(IOpenGenericBase_DiamondA))] + [JsonSerializable(typeof(IOpenGenericBase_DiamondB))] + [JsonSerializable(typeof(IOpenGenericBase_InterfaceHierarchy))] + [JsonSerializable(typeof(IOpenGenericBase_InterfaceWrapped>))] + [JsonSerializable(typeof(IOpenGenericBase_MultiBaseA))] + [JsonSerializable(typeof(IOpenGenericBase_MultiBaseB))] + [JsonSerializable(typeof(IOpenGenericBase_MultiCtor))] + [JsonSerializable(typeof(IVarCovBase))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(OpenGenericBase))] + [JsonSerializable(typeof(OpenGenericBase_ArrayArg))] + [JsonSerializable(typeof(OpenGenericBase_IntDisc))] + [JsonSerializable(typeof(OpenGenericBase_KvpArg>))] + [JsonSerializable(typeof(OpenGenericBase_Mixed))] + [JsonSerializable(typeof(OpenGenericBase_Multi))] + [JsonSerializable(typeof(OpenGenericBase_MultiLevel>))] + [JsonSerializable(typeof(OpenGenericBase_Partial))] + [JsonSerializable(typeof(OpenGenericBase_PartiallyConcrete))] + [JsonSerializable(typeof(OpenGenericBase_Reordered))] + [JsonSerializable(typeof(OpenGenericBase_StringDisc))] + [JsonSerializable(typeof(OpenGenericBase_Tuple<(int, string)>))] + [JsonSerializable(typeof(OpenGenericBase_Wrapped>))] + [JsonSerializable(typeof(Peano))] + [JsonSerializable(typeof(PetOwner))] + [JsonSerializable(typeof(PolymorphicAbstractClass))] + [JsonSerializable(typeof(PolymorphicBaseWithCustomDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass))] + [JsonSerializable(typeof(PolymorphicClass[]))] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_NoTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_TypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName))] + [JsonSerializable(typeof(PolymorphicClass_NoTypeDiscriminators))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator[]))] + [JsonSerializable(typeof(PolymorphicClassWithDerivedCollections))] + [JsonSerializable(typeof(PolymorphicClassWithEscapedTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicDictionary))] + [JsonSerializable(typeof(PolymorphicInterface))] + [JsonSerializable(typeof(PolymorphicInterfaceWithInterfaceDerivedType))] + [JsonSerializable(typeof(PolymorphicIntList))] + [JsonSerializable(typeof(PolymorphicList))] + [JsonSerializable(typeof(PolymorphicTypeWithExtensionDataConflictingPropertyName))] + [JsonSerializable(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName))] + [JsonSerializable(typeof(TestNode))] + [JsonSerializable(typeof(UnsupportedAttrClassifiedAnimal))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithCustomTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterfaceWithInterfaceDerivedType.DerivedClass), TypeInfoPropertyName = "PolymorphicInterfaceWithInterfaceDerivedType_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_CustomConverter_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_CustomConverter_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName_DerivedClass")] + [JsonSerializable(typeof(PolymorphicAbstractClass.Derived), TypeInfoPropertyName = "PolymorphicAbstractClass_Derived")] + [JsonSerializable(typeof(PolymorphicClassWithEscapedTypeDiscriminator.Derived), TypeInfoPropertyName = "PolymorphicClassWithEscapedTypeDiscriminator_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName.Derived), TypeInfoPropertyName = "PolymorphicTypeWithIgnoredConflictingPropertyName_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithExtensionDataConflictingPropertyName.Derived), TypeInfoPropertyName = "PolymorphicTypeWithExtensionDataConflictingPropertyName_Derived")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass1_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass1_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass1_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass1_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass2_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass2_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass2_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass2_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedAbstractClass.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedAbstractClass_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedCollection_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedCollection_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedCollection_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedCollection_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedDictionary_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedDictionary_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedDictionary_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedDictionary_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithConstructor_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithConstructor_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithCustomConverter_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithCustomConverter_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithCustomConverter_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithCustomConverter_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterface.DerivedClass_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicInterface_DerivedClass_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterface.DerivedClass_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicInterface_DerivedClass_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(object))] + [JsonSerializable(typeof(Customer))] + [JsonSerializable(typeof(ObjectWithObjectProperties))] + [JsonSerializable(typeof(Person))] + [JsonSerializable(typeof(ConstraintBase>))] + [JsonSerializable(typeof(IVarBivariantBase))] + [JsonSerializable(typeof(IVarContraBase))] + [JsonSerializable(typeof(NestedBaseB.NestedBoxB>))] + [JsonSerializable(typeof(OpenGenericBase_ComplexArg>))] + [JsonSerializable(typeof(OpenGenericBase_Nested))] + [JsonSerializable(typeof(OpenGenericBase_NullableArg))] + [JsonSerializable(typeof(OpenGenericBase_StringDisc))] + [JsonSerializable(typeof(PolymorphicAbstractClassWithAbstractClassDerivedType))] + [JsonSerializable(typeof(PolymorphicClasWithDuplicateTypeDiscriminators))] + [JsonSerializable(typeof(PolymorphicClassWithConstructor))] + [JsonSerializable(typeof(PolymorphicClassWithDuplicateDerivedTypeRegistrations))] + [JsonSerializable(typeof(PolymorphicClassWithNonAssignableDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithObjectDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithStructDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithoutDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClass_WithBaseTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_WithBaseTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_WithBaseTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass))] + [JsonSerializable(typeof(PolymorphicGenericClass))] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase))] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(SimpleTestClassWithObject))] + [JsonSerializable(typeof(PolymorphicAbstractClassWithAbstractClassDerivedType.DerivedClass), TypeInfoPropertyName = "PolymorphicAbstractClassWithAbstractClassDerivedType_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClassWithConstructor.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithConstructor_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClassWithConstructor.DerivedCollection), TypeInfoPropertyName = "PolymorphicClassWithConstructor_DerivedCollection")] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator.DerivedCollection), TypeInfoPropertyName = "PolymorphicClassWithCustomTypeDiscriminator_DerivedCollection")] + [JsonSerializable(typeof(PolymorphicClassWithDuplicateDerivedTypeRegistrations.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithDuplicateDerivedTypeRegistrations_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_NoTypeDiscriminators.DerivedClass2), TypeInfoPropertyName = "PolymorphicClass_NoTypeDiscriminators_DerivedClass2")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_WithDerivedPolymorphicClass_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass.DerivedClass.DerivedClass2), TypeInfoPropertyName = "PolymorphicClass_WithDerivedPolymorphicClass_DerivedClass_DerivedClass2")] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase.Derived), TypeInfoPropertyName = "PolymorphicTypeWithConflictingPropertyNameAtBase_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived.Derived), TypeInfoPropertyName = "PolymorphicTypeWithConflictingPropertyNameAtDerived_Derived")] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(short[]))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string[]))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(HashSet))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(long[]))] + [JsonSerializable(typeof(JsonNode))] + [JsonSerializable(typeof(JsonObject))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Stack))] + [JsonSerializable(typeof(System.Collections.IEnumerable))] + [JsonSerializable(typeof(ushort[]))] + [JsonSerializable(typeof(int[]))] + [JsonSerializable(typeof(uint[]))] + [JsonSerializable(typeof(ulong[]))] + [JsonSerializable(typeof(byte[]))] + [JsonSerializable(typeof(sbyte[]))] + [JsonSerializable(typeof(char[]))] + [JsonSerializable(typeof(bool[]))] + [JsonSerializable(typeof(float[]))] + [JsonSerializable(typeof(double[]))] + [JsonSerializable(typeof(decimal[]))] + [JsonSerializable(typeof(System.DateTime[]))] + [JsonSerializable(typeof(SampleEnum[]))] + [JsonSerializable(typeof(int?[]))] + [JsonSerializable(typeof(System.Guid))] + [JsonSerializable(typeof(SimpleStruct))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(LinkedList))] + [JsonSerializable(typeof(SortedSet))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableArray))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableHashSet))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableSortedSet))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableDictionary))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableSortedDictionary))] + [JsonSerializable(typeof(System.Collections.IList))] + [JsonSerializable(typeof(System.Collections.ICollection))] + [JsonSerializable(typeof(IEnumerable))] + [JsonSerializable(typeof(IList))] + [JsonSerializable(typeof(ICollection))] + [JsonSerializable(typeof(IReadOnlyCollection))] + [JsonSerializable(typeof(IReadOnlyList))] + [JsonSerializable(typeof(ISet))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableSet))] + [JsonSerializable(typeof(HashSet))] + [JsonSerializable(typeof(Stack))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(LinkedList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableArray))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableHashSet))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(byte))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] + internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext + { + } + } + + public sealed partial class PolymorphicTests_Metadata_AsyncStream : PolymorphicTests + { + public PolymorphicTests_Metadata_AsyncStream() + : base(new AsyncStreamSerializerWrapper(PolymorphicTests_Metadata.PolymorphicTestsContext_Metadata.Default)) + { + } + } + + public sealed partial class PolymorphicTests_Default : PolymorphicTests + { + public PolymorphicTests_Default() + : base(new StringSerializerWrapper(PolymorphicTestsContext_Default.Default)) + { + } + + [JsonSerializable(typeof(AttrClassifiedAnimal))] + [JsonSerializable(typeof(BinaryTree))] + [JsonSerializable(typeof(ClassifiedAnimalBase))] + [JsonSerializable(typeof(ClassifiedAnimalBase[]))] + [JsonSerializable(typeof(ClassifiedAnimalDeepHierarchy))] + [JsonSerializable(typeof(ClassifiedShape))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(DrawingCanvas))] + [JsonSerializable(typeof(FactoryClassifiedAnimal))] + [JsonSerializable(typeof(IEnumerable[]))] + [JsonSerializable(typeof(IEnumerable>[]))] + [JsonSerializable(typeof(IOpenGenericBase))] + [JsonSerializable(typeof(IOpenGenericBase_DiamondA))] + [JsonSerializable(typeof(IOpenGenericBase_DiamondB))] + [JsonSerializable(typeof(IOpenGenericBase_InterfaceHierarchy))] + [JsonSerializable(typeof(IOpenGenericBase_InterfaceWrapped>))] + [JsonSerializable(typeof(IOpenGenericBase_MultiBaseA))] + [JsonSerializable(typeof(IOpenGenericBase_MultiBaseB))] + [JsonSerializable(typeof(IOpenGenericBase_MultiCtor))] + [JsonSerializable(typeof(IVarCovBase))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(OpenGenericBase))] + [JsonSerializable(typeof(OpenGenericBase_ArrayArg))] + [JsonSerializable(typeof(OpenGenericBase_IntDisc))] + [JsonSerializable(typeof(OpenGenericBase_KvpArg>))] + [JsonSerializable(typeof(OpenGenericBase_Mixed))] + [JsonSerializable(typeof(OpenGenericBase_Multi))] + [JsonSerializable(typeof(OpenGenericBase_MultiLevel>))] + [JsonSerializable(typeof(OpenGenericBase_Partial))] + [JsonSerializable(typeof(OpenGenericBase_PartiallyConcrete))] + [JsonSerializable(typeof(OpenGenericBase_Reordered))] + [JsonSerializable(typeof(OpenGenericBase_StringDisc))] + [JsonSerializable(typeof(OpenGenericBase_Tuple<(int, string)>))] + [JsonSerializable(typeof(OpenGenericBase_Wrapped>))] + [JsonSerializable(typeof(Peano))] + [JsonSerializable(typeof(PetOwner))] + [JsonSerializable(typeof(PolymorphicAbstractClass))] + [JsonSerializable(typeof(PolymorphicBaseWithCustomDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass))] + [JsonSerializable(typeof(PolymorphicClass[]))] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_NoTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_TypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName))] + [JsonSerializable(typeof(PolymorphicClass_NoTypeDiscriminators))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator[]))] + [JsonSerializable(typeof(PolymorphicClassWithDerivedCollections))] + [JsonSerializable(typeof(PolymorphicClassWithEscapedTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicDictionary))] + [JsonSerializable(typeof(PolymorphicInterface))] + [JsonSerializable(typeof(PolymorphicInterfaceWithInterfaceDerivedType))] + [JsonSerializable(typeof(PolymorphicIntList))] + [JsonSerializable(typeof(PolymorphicList))] + [JsonSerializable(typeof(PolymorphicTypeWithExtensionDataConflictingPropertyName))] + [JsonSerializable(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName))] + [JsonSerializable(typeof(TestNode))] + [JsonSerializable(typeof(UnsupportedAttrClassifiedAnimal))] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithCustomTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterfaceWithInterfaceDerivedType.DerivedClass), TypeInfoPropertyName = "PolymorphicInterfaceWithInterfaceDerivedType_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_CustomConverter_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_CustomConverter_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_CustomConverter_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_InvalidCustomTypeDiscriminatorPropertyName_DerivedClass")] + [JsonSerializable(typeof(PolymorphicAbstractClass.Derived), TypeInfoPropertyName = "PolymorphicAbstractClass_Derived")] + [JsonSerializable(typeof(PolymorphicClassWithEscapedTypeDiscriminator.Derived), TypeInfoPropertyName = "PolymorphicClassWithEscapedTypeDiscriminator_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName.Derived), TypeInfoPropertyName = "PolymorphicTypeWithIgnoredConflictingPropertyName_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithExtensionDataConflictingPropertyName.Derived), TypeInfoPropertyName = "PolymorphicTypeWithExtensionDataConflictingPropertyName_Derived")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass1_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass1_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass1_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass1_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass2_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass2_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClass2_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClass2_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedAbstractClass.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedAbstractClass_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedCollection_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedCollection_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedCollection_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedCollection_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedDictionary_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedDictionary_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedDictionary_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedDictionary_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithConstructor_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithConstructor_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithCustomConverter_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithCustomConverter_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass.DerivedClassWithCustomConverter_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_DerivedClassWithCustomConverter_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterface.DerivedClass_NoTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicInterface_DerivedClass_NoTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicInterface.DerivedClass_TypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicInterface_DerivedClass_TypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(object))] + [JsonSerializable(typeof(Customer))] + [JsonSerializable(typeof(ObjectWithObjectProperties))] + [JsonSerializable(typeof(Person))] + [JsonSerializable(typeof(ConstraintBase>))] + [JsonSerializable(typeof(IVarBivariantBase))] + [JsonSerializable(typeof(IVarContraBase))] + [JsonSerializable(typeof(NestedBaseB.NestedBoxB>))] + [JsonSerializable(typeof(OpenGenericBase_ComplexArg>))] + [JsonSerializable(typeof(OpenGenericBase_Nested))] + [JsonSerializable(typeof(OpenGenericBase_NullableArg))] + [JsonSerializable(typeof(OpenGenericBase_StringDisc))] + [JsonSerializable(typeof(PolymorphicAbstractClassWithAbstractClassDerivedType))] + [JsonSerializable(typeof(PolymorphicClasWithDuplicateTypeDiscriminators))] + [JsonSerializable(typeof(PolymorphicClassWithConstructor))] + [JsonSerializable(typeof(PolymorphicClassWithDuplicateDerivedTypeRegistrations))] + [JsonSerializable(typeof(PolymorphicClassWithNonAssignableDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithObjectDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithStructDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClassWithoutDerivedTypeAttribute))] + [JsonSerializable(typeof(PolymorphicClass_WithBaseTypeDiscriminator))] + [JsonSerializable(typeof(PolymorphicClass_WithBaseTypeDiscriminator.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_WithBaseTypeDiscriminator_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass))] + [JsonSerializable(typeof(PolymorphicGenericClass))] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase))] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(SimpleTestClassWithObject))] + [JsonSerializable(typeof(PolymorphicAbstractClassWithAbstractClassDerivedType.DerivedClass), TypeInfoPropertyName = "PolymorphicAbstractClassWithAbstractClassDerivedType_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClassWithConstructor.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithConstructor_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClassWithConstructor.DerivedCollection), TypeInfoPropertyName = "PolymorphicClassWithConstructor_DerivedCollection")] + [JsonSerializable(typeof(PolymorphicClassWithCustomTypeDiscriminator.DerivedCollection), TypeInfoPropertyName = "PolymorphicClassWithCustomTypeDiscriminator_DerivedCollection")] + [JsonSerializable(typeof(PolymorphicClassWithDuplicateDerivedTypeRegistrations.DerivedClass), TypeInfoPropertyName = "PolymorphicClassWithDuplicateDerivedTypeRegistrations_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_NoTypeDiscriminators.DerivedClass2), TypeInfoPropertyName = "PolymorphicClass_NoTypeDiscriminators_DerivedClass2")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass.DerivedClass), TypeInfoPropertyName = "PolymorphicClass_WithDerivedPolymorphicClass_DerivedClass")] + [JsonSerializable(typeof(PolymorphicClass_WithDerivedPolymorphicClass.DerivedClass.DerivedClass2), TypeInfoPropertyName = "PolymorphicClass_WithDerivedPolymorphicClass_DerivedClass_DerivedClass2")] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase.Derived), TypeInfoPropertyName = "PolymorphicTypeWithConflictingPropertyNameAtBase_Derived")] + [JsonSerializable(typeof(PolymorphicTypeWithConflictingPropertyNameAtDerived.Derived), TypeInfoPropertyName = "PolymorphicTypeWithConflictingPropertyNameAtDerived_Derived")] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(short[]))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string[]))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco>))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(HashSet))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(long[]))] + [JsonSerializable(typeof(JsonNode))] + [JsonSerializable(typeof(JsonObject))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary>))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Stack))] + [JsonSerializable(typeof(System.Collections.IEnumerable))] + [JsonSerializable(typeof(ushort[]))] + [JsonSerializable(typeof(int[]))] + [JsonSerializable(typeof(uint[]))] + [JsonSerializable(typeof(ulong[]))] + [JsonSerializable(typeof(byte[]))] + [JsonSerializable(typeof(sbyte[]))] + [JsonSerializable(typeof(char[]))] + [JsonSerializable(typeof(bool[]))] + [JsonSerializable(typeof(float[]))] + [JsonSerializable(typeof(double[]))] + [JsonSerializable(typeof(decimal[]))] + [JsonSerializable(typeof(System.DateTime[]))] + [JsonSerializable(typeof(SampleEnum[]))] + [JsonSerializable(typeof(int?[]))] + [JsonSerializable(typeof(System.Guid))] + [JsonSerializable(typeof(SimpleStruct))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(LinkedList))] + [JsonSerializable(typeof(SortedSet))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableArray))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableHashSet))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableSortedSet))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableDictionary))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableSortedDictionary))] + [JsonSerializable(typeof(System.Collections.IList))] + [JsonSerializable(typeof(System.Collections.ICollection))] + [JsonSerializable(typeof(IEnumerable))] + [JsonSerializable(typeof(IList))] + [JsonSerializable(typeof(ICollection))] + [JsonSerializable(typeof(IReadOnlyCollection))] + [JsonSerializable(typeof(IReadOnlyList))] + [JsonSerializable(typeof(ISet))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.IImmutableSet))] + [JsonSerializable(typeof(HashSet))] + [JsonSerializable(typeof(Stack))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(LinkedList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableArray))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableList))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableStack))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableQueue))] + [JsonSerializable(typeof(System.Collections.Immutable.ImmutableHashSet))] + [JsonSerializable(typeof(GenericPoco))] + [JsonSerializable(typeof(byte))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] + internal sealed partial class PolymorphicTestsContext_Default : JsonSerializerContext + { + } + } + + public sealed partial class PolymorphicTests_Default_AsyncStream : PolymorphicTests + { + public PolymorphicTests_Default_AsyncStream() + : base(new AsyncStreamSerializerWrapper(PolymorphicTests_Default.PolymorphicTestsContext_Default.Default)) + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphismTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphismTests.cs deleted file mode 100644 index a8ae6c0991b189..00000000000000 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PolymorphismTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -// 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.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Xunit; - -namespace System.Text.Json.SourceGeneration.Tests -{ - public static partial class PolymorphismTests - { - [Fact] - public static void PolymorphismOptions_AreGenerated() - { - JsonTypeInfo typeInfo = PolymorphismTestsContext.Default.SourceGenPolymorphicBase; - JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); - - Assert.True(options.IgnoreUnrecognizedTypeDiscriminators); - Assert.Equal(JsonUnknownDerivedTypeHandling.FallBackToBaseType, options.UnknownDerivedTypeHandling); - Assert.Equal("$kind", options.TypeDiscriminatorPropertyName); - Assert.Collection( - options.DerivedTypes, - derivedType => - { - Assert.Equal(typeof(SourceGenStringDiscriminatorDerived), derivedType.DerivedType); - Assert.Equal("string-derived", derivedType.TypeDiscriminator); - }, - derivedType => - { - Assert.Equal(typeof(SourceGenIntDiscriminatorDerived), derivedType.DerivedType); - Assert.Equal(42, derivedType.TypeDiscriminator); - }); - } - - [Fact] - public static void PolymorphicTypeClassifier_IsGeneratedAndVisibleToModifier() - { - bool modifierObservedClassifier = false; - IJsonTypeInfoResolver resolver = PolymorphismTestsContext.Default.WithAddedModifier(typeInfo => - { - if (typeInfo.Type == typeof(SourceGenClassifiedAnimal)) - { - Assert.NotNull(typeInfo.PolymorphismOptions); - Assert.NotNull(typeInfo.TypeClassifier); - modifierObservedClassifier = true; - } - }); - - var options = new JsonSerializerOptions - { - TypeInfoResolver = resolver, - }; - - SourceGenClassifiedAnimal? result = JsonSerializer.Deserialize( - """{"Name":"Rex","Breed":"Labrador"}""", - options); - - SourceGenClassifiedDog dog = Assert.IsType(result); - Assert.Equal("Rex", dog.Name); - Assert.Equal("Labrador", dog.Breed); - Assert.True(modifierObservedClassifier); - } - - [Fact] - public static void CollectionPolymorphismOptions_AreGenerated() - { - JsonTypeInfo typeInfo = PolymorphismTestsContext.Default.SourceGenPolymorphicIntList; - JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); - - Assert.Equal("$kind", options.TypeDiscriminatorPropertyName); - JsonDerivedType derivedType = Assert.Single(options.DerivedTypes); - Assert.Equal(typeof(SourceGenPolymorphicIntListDerived), derivedType.DerivedType); - Assert.Equal("derived-list", derivedType.TypeDiscriminator); - } - - [Fact] - public static void OpenGenericDerivedType_PartiallyConcrete_RoundTrips() - { - // SourceGenOpenGenericDerived : SourceGenOpenGenericBase registered on - // SourceGenOpenGenericBase. Position 0 (T) unifies to string; - // position 1 (concrete int) matches. The generated metadata for the closed base - // must contain SourceGenOpenGenericDerived as the resolved derived type. - JsonTypeInfo typeInfo = PolymorphismTestsContext.Default.SourceGenOpenGenericBaseStringInt32; - JsonPolymorphismOptions options = Assert.IsType(typeInfo.PolymorphismOptions); - JsonDerivedType derivedType = Assert.Single(options.DerivedTypes); - Assert.Equal(typeof(SourceGenOpenGenericDerived), derivedType.DerivedType); - Assert.Equal("derived", derivedType.TypeDiscriminator); - - SourceGenOpenGenericBase value = new SourceGenOpenGenericDerived { Extra = "hello" }; - string json = JsonSerializer.Serialize(value, typeInfo); - Assert.Contains("\"$type\":\"derived\"", json); - - var result = JsonSerializer.Deserialize(json, typeInfo); - var d = Assert.IsType>(result); - Assert.Equal("hello", d.Extra); - } - - [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(SourceGenPolymorphicBase))] - [JsonSerializable(typeof(SourceGenClassifiedAnimal))] - [JsonSerializable(typeof(SourceGenPolymorphicIntList))] - [JsonSerializable(typeof(SourceGenOpenGenericBase))] - internal sealed partial class PolymorphismTestsContext : JsonSerializerContext - { - } - } - - [JsonDerivedType(typeof(SourceGenOpenGenericDerived<>), "derived")] - public class SourceGenOpenGenericBase - { - public T1? Value1 { get; set; } - public T2? Value2 { get; set; } - } - - public sealed class SourceGenOpenGenericDerived : SourceGenOpenGenericBase - { - public T? Extra { get; set; } - } - - [JsonPolymorphic( - TypeDiscriminatorPropertyName = "$kind", - IgnoreUnrecognizedTypeDiscriminators = true, - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] - [JsonDerivedType(typeof(SourceGenStringDiscriminatorDerived), "string-derived")] - [JsonDerivedType(typeof(SourceGenIntDiscriminatorDerived), 42)] - public class SourceGenPolymorphicBase - { - public string? Value { get; set; } - } - - public sealed class SourceGenStringDiscriminatorDerived : SourceGenPolymorphicBase - { - public string? StringValue { get; set; } - } - - public sealed class SourceGenIntDiscriminatorDerived : SourceGenPolymorphicBase - { - public int IntValue { get; set; } - } - - [JsonPolymorphic(TypeClassifier = typeof(SourceGenAnimalClassifierFactory))] - [JsonDerivedType(typeof(SourceGenClassifiedDog), "dog")] - [JsonDerivedType(typeof(SourceGenClassifiedCat), "cat")] - public class SourceGenClassifiedAnimal - { - public string? Name { get; set; } - } - - public sealed class SourceGenClassifiedDog : SourceGenClassifiedAnimal - { - public string? Breed { get; set; } - } - - public sealed class SourceGenClassifiedCat : SourceGenClassifiedAnimal - { - public int Lives { get; set; } - } - - [JsonPolymorphic(TypeDiscriminatorPropertyName = "$kind")] - [JsonDerivedType(typeof(SourceGenPolymorphicIntListDerived), "derived-list")] - public class SourceGenPolymorphicIntList : List - { - } - - public sealed class SourceGenPolymorphicIntListDerived : SourceGenPolymorphicIntList - { - } - - public sealed class SourceGenAnimalClassifierFactory : JsonTypeClassifierFactory - { - public override JsonTypeClassifier CreateJsonClassifier(JsonTypeClassifierContext context, JsonSerializerOptions options) - { - Assert.Equal(JsonTypeClassifierKind.PolymorphicType, context.Kind); - Assert.Equal(typeof(SourceGenClassifiedAnimal), context.DeclaringType); - Assert.Equal("$type", context.TypeDiscriminatorPropertyName); - Assert.Collection( - context.DerivedTypes, - derivedType => - { - Assert.Equal(typeof(SourceGenClassifiedDog), derivedType.DerivedType); - Assert.Equal("dog", derivedType.TypeDiscriminator); - }, - derivedType => - { - Assert.Equal(typeof(SourceGenClassifiedCat), derivedType.DerivedType); - Assert.Equal("cat", derivedType.TypeDiscriminator); - }); - - return static (ref Utf8JsonReader reader) => - { - using JsonDocument document = JsonDocument.ParseValue(ref reader); - JsonElement root = document.RootElement; - - if (root.TryGetProperty("Breed", out _)) - { - return typeof(SourceGenClassifiedDog); - } - - if (root.TryGetProperty("Lives", out _)) - { - return typeof(SourceGenClassifiedCat); - } - - return null; - }; - } - } -} 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 992ee1ae672abf..2233ab2704d2e9 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 @@ -129,6 +129,9 @@ + + + @@ -142,7 +145,7 @@ - + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs index fd2eee55394bcb..5102ff6201695d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs @@ -1,773 +1,60 @@ // 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.Generic; -using System.Collections.Immutable; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization.Metadata; -using System.Threading.Tasks; -using Xunit; - namespace System.Text.Json.Serialization.Tests { - public class PolymorphicTests_Span : PolymorphicTests + public sealed class PolymorphicTests_Span : PolymorphicTests { public PolymorphicTests_Span() : base(JsonSerializerWrapper.SpanSerializer) { } } - public class PolymorphicTests_String : PolymorphicTests + public sealed class PolymorphicTests_String : PolymorphicTests { public PolymorphicTests_String() : base(JsonSerializerWrapper.StringSerializer) { } } - public class PolymorphicTests_AsyncStream : PolymorphicTests + public sealed class PolymorphicTests_AsyncStream : PolymorphicTests { public PolymorphicTests_AsyncStream() : base(JsonSerializerWrapper.AsyncStreamSerializer) { } } - public class PolymorphicTests_AsyncStreamWithSmallBuffer : PolymorphicTests + public sealed class PolymorphicTests_AsyncStreamWithSmallBuffer : PolymorphicTests { public PolymorphicTests_AsyncStreamWithSmallBuffer() : base(JsonSerializerWrapper.AsyncStreamSerializerWithSmallBuffer) { } } - public class PolymorphicTests_SyncStream : PolymorphicTests + public sealed class PolymorphicTests_SyncStream : PolymorphicTests { public PolymorphicTests_SyncStream() : base(JsonSerializerWrapper.SyncStreamSerializer) { } } - public class PolymorphicTests_Writer : PolymorphicTests + public sealed class PolymorphicTests_Writer : PolymorphicTests { public PolymorphicTests_Writer() : base(JsonSerializerWrapper.ReaderWriterSerializer) { } } - public class PolymorphicTests_Document : PolymorphicTests + public sealed class PolymorphicTests_Document : PolymorphicTests { public PolymorphicTests_Document() : base(JsonSerializerWrapper.DocumentSerializer) { } } - public class PolymorphicTests_Element : PolymorphicTests + public sealed class PolymorphicTests_Element : PolymorphicTests { public PolymorphicTests_Element() : base(JsonSerializerWrapper.ElementSerializer) { } } - public class PolymorphicTests_Node : PolymorphicTests + public sealed class PolymorphicTests_Node : PolymorphicTests { public PolymorphicTests_Node() : base(JsonSerializerWrapper.NodeSerializer) { } } - public class PolymorphicTests_Pipe : PolymorphicTests + public sealed class PolymorphicTests_Pipe : PolymorphicTests { public PolymorphicTests_Pipe() : base(JsonSerializerWrapper.AsyncPipeSerializer) { } } - public class PolymorphicTests_PipeWithSmallBuffer : PolymorphicTests + public sealed class PolymorphicTests_PipeWithSmallBuffer : PolymorphicTests { public PolymorphicTests_PipeWithSmallBuffer() : base(JsonSerializerWrapper.AsyncPipeSerializerWithSmallBuffer) { } } - - public abstract partial class PolymorphicTests : SerializerTests - { - public PolymorphicTests(JsonSerializerWrapper serializer) : base(serializer) - { - } - - [Theory] - [InlineData(1, "1")] - [InlineData("stringValue", """ - "stringValue" - """)] - [InlineData(true, "true")] - [InlineData(null, "null")] - [InlineData(new int[] { 1, 2, 3}, "[1,2,3]")] - public async Task PrimitivesAsRootObject(object? value, string expectedJson) - { - string json = await Serializer.SerializeWrapper(value); - Assert.Equal(expectedJson, json); - json = await Serializer.SerializeWrapper(value, typeof(object)); - Assert.Equal(expectedJson, json); - - var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; - JsonTypeInfo objectTypeInfo = options.GetTypeInfo(); - json = await Serializer.SerializeWrapper(value, objectTypeInfo); - Assert.Equal(expectedJson, json); - } - - [Fact] - public void ReadPrimitivesFail() - { - Assert.Throws(() => JsonSerializer.Deserialize(@"")); - Assert.Throws(() => JsonSerializer.Deserialize(@"a")); - } - - [Fact] - public void ParseUntyped() - { - object obj = JsonSerializer.Deserialize(""" - "hello" - """); - Assert.IsType(obj); - JsonElement element = (JsonElement)obj; - Assert.Equal(JsonValueKind.String, element.ValueKind); - Assert.Equal("hello", element.GetString()); - - obj = JsonSerializer.Deserialize(@"true"); - element = (JsonElement)obj; - Assert.Equal(JsonValueKind.True, element.ValueKind); - Assert.True(element.GetBoolean()); - - obj = JsonSerializer.Deserialize(@"null"); - Assert.Null(obj); - - obj = JsonSerializer.Deserialize("[]"); - element = (JsonElement)obj; - Assert.Equal(JsonValueKind.Array, element.ValueKind); - } - - [Fact] - public async Task ArrayAsRootObject() - { - const string ExpectedJson = """[1,true,{"City":"MyCity"},null,"foo"]"""; - const string ReversedExpectedJson = """["foo",null,{"City":"MyCity"},true,1]"""; - - string[] expectedObjects = { """ - "foo" - """, @"null", """{"City":"MyCity"}""", @"true", @"1" }; - - var address = new Address(); - address.Initialize(); - - var array = new object[] { 1, true, address, null, "foo" }; - string json = await Serializer.SerializeWrapper(array); - Assert.Equal(ExpectedJson, json); - - var dictionary = new Dictionary { { "City", "MyCity" } }; - var arrayWithDictionary = new object[] { 1, true, dictionary, null, "foo" }; - json = await Serializer.SerializeWrapper(arrayWithDictionary); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(array); - Assert.Equal(ExpectedJson, json); - - List list = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(list); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(list); - Assert.Equal(ExpectedJson, json); - - IEnumerable ienumerable = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(ienumerable); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(ienumerable); - Assert.Equal(ExpectedJson, json); - - IList ilist = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(ilist); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(ilist); - Assert.Equal(ExpectedJson, json); - - ICollection icollection = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(icollection); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(icollection); - Assert.Equal(ExpectedJson, json); - - IEnumerable genericIEnumerable = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(genericIEnumerable); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(genericIEnumerable); - Assert.Equal(ExpectedJson, json); - - IList genericIList = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(genericIList); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(genericIList); - Assert.Equal(ExpectedJson, json); - - ICollection genericICollection = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(genericICollection); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(genericICollection); - Assert.Equal(ExpectedJson, json); - - IReadOnlyCollection genericIReadOnlyCollection = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(genericIReadOnlyCollection); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(genericIReadOnlyCollection); - Assert.Equal(ExpectedJson, json); - - IReadOnlyList genericIReadonlyList = new List { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(genericIReadonlyList); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(genericIReadonlyList); - Assert.Equal(ExpectedJson, json); - - ISet iset = new HashSet { 1, true, address, null, "foo" }; - json = await Serializer.SerializeWrapper(iset); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(iset); - Assert.Equal(ExpectedJson, json); - - Stack stack = new Stack(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(stack); - Assert.Equal(ReversedExpectedJson, json); - - json = await Serializer.SerializeWrapper(stack); - Assert.Equal(ReversedExpectedJson, json); - - Queue queue = new Queue(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(queue); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(queue); - Assert.Equal(ExpectedJson, json); - - HashSet hashset = new HashSet(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(hashset); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(hashset); - Assert.Equal(ExpectedJson, json); - - LinkedList linkedlist = new LinkedList(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(linkedlist); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(linkedlist); - Assert.Equal(ExpectedJson, json); - - ImmutableArray immutablearray = ImmutableArray.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(immutablearray); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(immutablearray); - Assert.Equal(ExpectedJson, json); - - IImmutableList iimmutablelist = ImmutableList.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(iimmutablelist); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(iimmutablelist); - Assert.Equal(ExpectedJson, json); - - IImmutableStack iimmutablestack = ImmutableStack.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(iimmutablestack); - Assert.Equal(ReversedExpectedJson, json); - - json = await Serializer.SerializeWrapper(iimmutablestack); - Assert.Equal(ReversedExpectedJson, json); - - IImmutableQueue iimmutablequeue = ImmutableQueue.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(iimmutablequeue); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(iimmutablequeue); - Assert.Equal(ExpectedJson, json); - - IImmutableSet iimmutableset = ImmutableHashSet.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(iimmutableset); - foreach (string obj in expectedObjects) - { - Assert.Contains(obj, json); - } - - json = await Serializer.SerializeWrapper(iimmutableset); - foreach (string obj in expectedObjects) - { - Assert.Contains(obj, json); - } - - ImmutableHashSet immutablehashset = ImmutableHashSet.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(immutablehashset); - foreach (string obj in expectedObjects) - { - Assert.Contains(obj, json); - } - - json = await Serializer.SerializeWrapper(immutablehashset); - foreach (string obj in expectedObjects) - { - Assert.Contains(obj, json); - } - - ImmutableList immutablelist = ImmutableList.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(immutablelist); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(immutablelist); - Assert.Equal(ExpectedJson, json); - - ImmutableStack immutablestack = ImmutableStack.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(immutablestack); - Assert.Equal(ReversedExpectedJson, json); - - json = await Serializer.SerializeWrapper(immutablestack); - Assert.Equal(ReversedExpectedJson, json); - - ImmutableQueue immutablequeue = ImmutableQueue.CreateRange(new List { 1, true, address, null, "foo" }); - json = await Serializer.SerializeWrapper(immutablequeue); - Assert.Equal(ExpectedJson, json); - - json = await Serializer.SerializeWrapper(immutablequeue); - Assert.Equal(ExpectedJson, json); - } - - [Fact] - public async Task SimpleTestClassAsRootObject() - { - // Sanity checks on test type. - Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyInt16").PropertyType); - Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyBooleanTrue").PropertyType); - Assert.Equal(typeof(object), typeof(SimpleTestClassWithObject).GetProperty("MyInt16Array").PropertyType); - - var obj = new SimpleTestClassWithObject(); - obj.Initialize(); - - // Verify with actual type. - string json = await Serializer.SerializeWrapper(obj); - Assert.Contains(""" - "MyInt16":1 - """, json); - Assert.Contains(""" - "MyBooleanTrue":true - """, json); - Assert.Contains(""" - "MyInt16Array":[1] - """, json); - - // Verify with object type. - json = await Serializer.SerializeWrapper(obj); - Assert.Contains(""" - "MyInt16":1 - """, json); - Assert.Contains(""" - "MyBooleanTrue":true - """, json); - Assert.Contains(""" - "MyInt16Array":[1] - """, json); - } - - [Fact] - public async Task NestedObjectAsRootObject() - { - void Verify(string json) - { - Assert.Contains(""" - "Address":{"City":"MyCity"} - """, json); - Assert.Contains(""" - "List":["Hello","World"] - """, json); - Assert.Contains(""" - "Array":["Hello","Again"] - """, json); - Assert.Contains(""" - "IEnumerable":["Hello","World"] - """, json); - Assert.Contains(""" - "IList":["Hello","World"] - """, json); - Assert.Contains(""" - "ICollection":["Hello","World"] - """, json); - Assert.Contains(""" - "IEnumerableT":["Hello","World"] - """, json); - Assert.Contains(""" - "IListT":["Hello","World"] - """, json); - Assert.Contains(""" - "ICollectionT":["Hello","World"] - """, json); - Assert.Contains(""" - "IReadOnlyCollectionT":["Hello","World"] - """, json); - Assert.Contains(""" - "IReadOnlyListT":["Hello","World"] - """, json); - Assert.Contains(""" - "ISetT":["Hello","World"] - """, json); - Assert.Contains(""" - "IReadOnlySetT":["Hello","World"] - """, json); - Assert.Contains(""" - "StackT":["World","Hello"] - """, json); - Assert.Contains(""" - "QueueT":["Hello","World"] - """, json); - Assert.Contains(""" - "HashSetT":["Hello","World"] - """, json); - Assert.Contains(""" - "LinkedListT":["Hello","World"] - """, json); - Assert.Contains(""" - "SortedSetT":["Hello","World"] - """, json); - Assert.Contains(""" - "ImmutableArrayT":["Hello","World"] - """, json); - Assert.Contains(""" - "IImmutableListT":["Hello","World"] - """, json); - Assert.Contains(""" - "IImmutableStackT":["World","Hello"] - """, json); - Assert.Contains(""" - "IImmutableQueueT":["Hello","World"] - """, json); - Assert.True(json.Contains(""" - "IImmutableSetT":["Hello","World"] - """) || json.Contains(""" - "IImmutableSetT":["World","Hello"] - """)); - Assert.True(json.Contains(""" - "ImmutableHashSetT":["Hello","World"] - """) || json.Contains(""" - "ImmutableHashSetT":["World","Hello"] - """)); - Assert.Contains(""" - "ImmutableListT":["Hello","World"] - """, json); - Assert.Contains(""" - "ImmutableStackT":["World","Hello"] - """, json); - Assert.Contains(""" - "ImmutableQueueT":["Hello","World"] - """, json); - Assert.Contains(""" - "ImmutableSortedSetT":["Hello","World"] - """, json); - Assert.Contains(""" - "NullableInt":42 - """, json); - Assert.Contains(""" - "Object":{} - """, json); - Assert.Contains(""" - "NullableIntArray":[null,42,null] - """, json); - } - - // Sanity checks on test type. - Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("Address").PropertyType); - Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("List").PropertyType); - Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("Array").PropertyType); - Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("NullableInt").PropertyType); - Assert.Equal(typeof(object), typeof(ObjectWithObjectProperties).GetProperty("NullableIntArray").PropertyType); - - var obj = new ObjectWithObjectProperties(); - - string json = await Serializer.SerializeWrapper(obj); - Verify(json); - - json = await Serializer.SerializeWrapper(obj); - Verify(json); - } - - [Fact] - public async Task NestedObjectAsRootObjectIgnoreNullable() - { - // Ensure that null properties are properly written and support ignore. - var obj = new ObjectWithObjectProperties(); - obj.NullableInt = null; - - string json = await Serializer.SerializeWrapper(obj); - Assert.Contains(""" - "NullableInt":null - """, json); - - JsonSerializerOptions options = new JsonSerializerOptions(); - options.IgnoreNullValues = true; - json = await Serializer.SerializeWrapper(obj, options); - Assert.DoesNotContain(""" - "NullableInt":null - """, json); - } - - [Fact] - public async Task StaticAnalysisBaseline() - { - Customer customer = new Customer(); - customer.Initialize(); - customer.Verify(); - - string json = await Serializer.SerializeWrapper(customer); - Customer deserializedCustomer = JsonSerializer.Deserialize(json); - deserializedCustomer.Verify(); - } - - [Fact] - public async Task StaticAnalysis() - { - Customer customer = new Customer(); - customer.Initialize(); - customer.Verify(); - - Person person = customer; - - // Generic inference used = - string json = await Serializer.SerializeWrapper(person); - - Customer deserializedCustomer = JsonSerializer.Deserialize(json); - - // We only serialized the Person base class, so the Customer fields should be default. - Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); - Assert.Equal(0, deserializedCustomer.CreditLimit); - ((Person)deserializedCustomer).VerifyNonVirtual(); - } - - [Fact] - public async Task WriteStringWithRuntimeType() - { - Customer customer = new Customer(); - customer.Initialize(); - customer.Verify(); - - Person person = customer; - - string json = await Serializer.SerializeWrapper(person, person.GetType()); - - Customer deserializedCustomer = JsonSerializer.Deserialize(json); - - // We serialized the Customer - Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); - deserializedCustomer.Verify(); - } - - [Fact] - public async Task StaticAnalysisWithRelationship() - { - UsaCustomer usaCustomer = new UsaCustomer(); - usaCustomer.Initialize(); - usaCustomer.Verify(); - - // Note: this could be typeof(UsaAddress) if we preserve objects created in the ctor. Currently we only preserve IEnumerables. - Assert.Equal(typeof(Address), usaCustomer.Address.GetType()); - - Customer customer = usaCustomer; - - // Generic inference used = - string json = await Serializer.SerializeWrapper(customer); - - UsaCustomer deserializedCustomer = JsonSerializer.Deserialize(json); - - // We only serialized the Customer base class - Assert.Equal(typeof(UsaCustomer), deserializedCustomer.GetType()); - Assert.Equal(typeof(Address), deserializedCustomer.Address.GetType()); - ((Customer)deserializedCustomer).VerifyNonVirtual(); - } - - [Fact] - public void PolymorphicInterface_NotSupported() - { - Assert.Throws(() => JsonSerializer.Deserialize("""{ "Value": "A value", "Thing": { "Number": 123 } }""")); - } - - [Fact] - public void GenericListOfInterface_WithInvalidJson_ThrowsJsonException() - { - Assert.Throws(() => JsonSerializer.Deserialize("false")); - Assert.Throws(() => JsonSerializer.Deserialize("{}")); - } - - [Fact] - public void GenericListOfInterface_WithValidJson_ThrowsNotSupportedException() - { - Assert.Throws(() => JsonSerializer.Deserialize("[{}]")); - } - - [Fact] - public void GenericDictionaryOfInterface_WithInvalidJson_ThrowsJsonException() - { - Assert.Throws(() => JsonSerializer.Deserialize("""{"":1}""")); - } - - [Fact] - public void GenericDictionaryOfInterface_WithValidJson_ThrowsNotSupportedException() - { - Assert.Throws(() => JsonSerializer.Deserialize("""{"":{}}""")); - } - - [Fact] - public async Task AnonymousType() - { - const string Expected = """{"x":1,"y":true}"""; - var value = new { x = 1, y = true }; - - // Strongly-typed. - string json = await Serializer.SerializeWrapper(value); - Assert.Equal(Expected, json); - - // Boxed. - object objValue = value; - json = await Serializer.SerializeWrapper(objValue); - Assert.Equal(Expected, json); - } - - [Fact] - public async Task CustomResolverWithFailingAncestorType_DoesNotSurfaceException() - { - var options = new JsonSerializerOptions - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - static typeInfo => - { - if (typeInfo.Type == typeof(MyThing) || - typeInfo.Type == typeof(IList)) - { - throw new InvalidOperationException("some latent custom resolution bug"); - } - } - } - } - }; - - object value = new MyDerivedThing { Number = 42 }; - string json = await Serializer.SerializeWrapper(value, options); - Assert.Equal("""{"Number":42}""", json); - - value = new int[] { 1, 2, 3 }; - json = await Serializer.SerializeWrapper(value, options); - Assert.Equal("[1,2,3]", json); - } - - class MyClass - { - public string Value { get; set; } - public IThing Thing { get; set; } - } - - interface IThing - { - int Number { get; set; } - } - - class MyThing : IThing - { - public int Number { get; set; } - } - - class MyDerivedThing : MyThing - { - } - - class MyThingCollection : List { } - - 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) - { - InvalidOperationException ex; - object value = Activator.CreateInstance(derivedType); - - ex = Assert.Throws(() => Serializer.GetTypeInfo(baseType)); - ValidateException(ex); - - ex = await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value, baseType)); - ValidateException(ex); - - ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", baseType)); - ValidateException(ex); - - void ValidateException(InvalidOperationException ex) - { - Assert.Contains($"The type '{derivedType}' contains property 'Type' that conflicts with an existing metadata property name.", ex.Message); - } - } - - [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] - [JsonDerivedType(typeof(Derived), nameof(Derived))] - public abstract class PolymorphicTypeWithConflictingPropertyNameAtBase - { - public string Type { get; set; } - - public class Derived : PolymorphicTypeWithConflictingPropertyNameAtBase - { - public string Name { get; set; } - } - } - - [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] - [JsonDerivedType(typeof(Derived), nameof(Derived))] - public abstract class PolymorphicTypeWithConflictingPropertyNameAtDerived - { - public class Derived : PolymorphicTypeWithConflictingPropertyNameAtDerived - { - public string Type { get; set; } - } - } - - [Fact] - public async Task PolymorphicTypeWithIgnoredConflictingPropertyName_Supported() - { - PolymorphicTypeWithIgnoredConflictingPropertyName value = new PolymorphicTypeWithIgnoredConflictingPropertyName.Derived(); - - JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName)); - Assert.NotNull(typeInfo); - - string json = await Serializer.SerializeWrapper(value); - Assert.Equal("""{"Type":"Derived"}""", json); - - value = await Serializer.DeserializeWrapper(json); - Assert.IsType(value); - } - - [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] - [JsonDerivedType(typeof(Derived), nameof(Derived))] - public abstract class PolymorphicTypeWithIgnoredConflictingPropertyName - { - [JsonIgnore] - public string Type { get; set; } - - public class Derived : PolymorphicTypeWithIgnoredConflictingPropertyName; - } - - [Fact] - public async Task PolymorphicTypeWithExtensionDataConflictingPropertyName_Supported() - { - PolymorphicTypeWithExtensionDataConflictingPropertyName value = new PolymorphicTypeWithExtensionDataConflictingPropertyName.Derived(); - - JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName)); - Assert.NotNull(typeInfo); - - string json = await Serializer.SerializeWrapper(value); - Assert.Equal("""{"Type":"Derived"}""", json); - - value = await Serializer.DeserializeWrapper("""{"Type":"Derived","extraProp":null}"""); - Assert.IsType(value); - Assert.Equal(1, value.Type.Count); - Assert.Contains("extraProp", value.Type); - } - - [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] - [JsonDerivedType(typeof(Derived), nameof(Derived))] - public abstract class PolymorphicTypeWithExtensionDataConflictingPropertyName - { - [JsonExtensionData] - public JsonObject Type { get; set; } - - public class Derived : PolymorphicTypeWithExtensionDataConflictingPropertyName; - } - } } 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 277fe8285c0a8d..7fed121a4f9c9b 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 @@ -98,6 +98,9 @@ + + + @@ -226,8 +229,6 @@ - - From 56cc0ec4e33604427c8f08900e32595a5a7bed43 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 23 Jun 2026 18:58:56 +0300 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs index 9e81eea1d48638..78275e18c54ced 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs @@ -695,7 +695,7 @@ public async Task PolymorphicTypeWithExtensionDataConflictingPropertyName_Suppor { PolymorphicTypeWithExtensionDataConflictingPropertyName value = new PolymorphicTypeWithExtensionDataConflictingPropertyName.Derived(); - JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithIgnoredConflictingPropertyName)); + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(PolymorphicTypeWithExtensionDataConflictingPropertyName)); Assert.NotNull(typeInfo); string json = await Serializer.SerializeWrapper(value); From b56d2bb53f476e686a75724a8b2a17fd5ee41323 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 24 Jun 2026 09:46:52 +0300 Subject: [PATCH 3/6] Address review: SkipTestException + ConditionalFact for SG-unreachable polymorphic configs; drop transient region marker - Convert 11 'if (Serializer.IsSourceGeneratedSerializer) return;' guards to 'throw new SkipTestException(...)' so xunit reports them as Skipped rather than silently Passed. Decorate the affected tests with [ConditionalFact] so the runtime SkipTestException is honored. - Remove the '#region Generation parity (folded from source-gen suite)' marker in PolymorphicTests.TypeClassifier.cs; git blame retains the history. Verified: - Reflection (11 wrappers): 6039 total, 0 failed, 0 skipped. - Source-gen (4 wrappers): 2196 total, 0 failed, 40 skipped (10 guards x 4 wrappers). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../PolymorphicTests.CustomTypeHierarchies.cs | 90 ++++++++++++------- .../Common/PolymorphicTests.TypeClassifier.cs | 4 - .../tests/Common/PolymorphicTests.cs | 10 ++- 3 files changed, 66 insertions(+), 38 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 4769af65885343..33c6a6d77dbaea 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.CustomTypeHierarchies.cs @@ -9,6 +9,7 @@ using System.Text.Encodings.Web; using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -2477,15 +2478,18 @@ public class PolymorphicClassWithoutDerivedTypeAttribute { } - [Fact] + [ConditionalFact] public async Task PolymorphicClassWithNullDerivedTypeAttribute_ThrowsInvalidOperationException() { - // Generator NRE: registering this type triggers an unhandled NullReferenceException - // in JsonSourceGenerator.Parser.cs (the JsonDerivedType ctor arg is dereferenced - // without a null check). No SYSLIB diagnostic is emitted, so the SG path cannot - // reach the runtime InvalidOperationException. Validated under reflection only - // pending a generator fix. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + // The JsonDerivedType ctor arg is dereferenced without a null check in + // JsonSourceGenerator.Parser.cs, so the generator throws NRE rather than emitting + // a SYSLIB diagnostic. Pending a generator fix, the runtime InvalidOperationException + // is validated under reflection only. + throw new SkipTestException("Generator NRE on [JsonDerivedType(null)] prevents reaching the runtime path."); + } + var value = new PolymorphicClassWithNullDerivedTypeAttribute(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2662,11 +2666,14 @@ public class DerivedClass : PolymorphicGenericClass } } - [Fact] + [ConditionalFact] public async Task PolymorphicDerivedGenericClass_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + PolymorphicDerivedGenericClass value = new PolymorphicDerivedGenericClass.DerivedClass(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2850,11 +2857,14 @@ public async Task OpenGenericDerivedType_DifferentTypeArguments_ProduceDifferent JsonTestHelper.AssertJsonEqual("""{"$type":"derived","Value":"hello"}""", strJson); } - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_NonGenericBase_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + var value = new NonGenericBaseWithOpenGenericDerived(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); } @@ -2865,11 +2875,14 @@ public class NonGenericBaseWithOpenGenericDerived public class OpenDerived : NonGenericBaseWithOpenGenericDerived; } - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_TypeArgsNotResolvable_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // Derived : Base - T cannot be determined from Base var value = new OpenGenericBase_Unresolvable(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); @@ -2883,11 +2896,14 @@ public class OpenGenericBase_Unresolvable public class OpenGenericDerived_Unresolvable : OpenGenericBase_Unresolvable; - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_GroundMismatchAgainstClosedBase_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // OpenGenericDerived_GroundMismatch : OpenGenericBase_GroundMismatch // registered on OpenGenericBase_GroundMismatch. // Position 0 (T) unifies with int, but position 1 (concrete int in derived's base @@ -3188,11 +3204,14 @@ public class OpenGenericDerived_Tuple : OpenGenericBase_Tuple<(T1, T2)> public (T1, T2) Pair { get; set; } } - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_AmbiguousInterfaceMatch_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // Impl : IBase, IBase> registered on IBase>. // Both ancestors unify (T=List via the first interface, T=int via the second). // Result: ambiguous, throws. @@ -3205,11 +3224,14 @@ public interface IOpenGenericBase_Ambiguous; public class OpenGenericImpl_Ambiguous : IOpenGenericBase_Ambiguous, IOpenGenericBase_Ambiguous>; - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_UnboundParameter_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // Derived : Base — T2 is unspeakable (not bound by the base type's args). var value = new OpenGenericBase_Unbound(); await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); @@ -3220,11 +3242,14 @@ public class OpenGenericBase_Unbound; public class OpenGenericDerived_Unbound : OpenGenericBase_Unbound; - [Fact] + [ConditionalFact] public async Task OpenGenericDerivedType_ConstraintViolation_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // Derived : Base where T : struct, registered on Base. // Constraint fails → InvalidOperationException. var value = new OpenGenericBase_StructConstraint(); @@ -3513,11 +3538,14 @@ public async Task Variance_BivariantInterface_BothViaVariance_DefaultThrows() // behavior. The reflection side has always handled these cases correctly because // Type.GetGenericArguments() returns enclosing+leaf args together. - [Fact] + [ConditionalFact] public async Task NestedGeneric_EnclosingMismatch_ThrowsInvalidOperationException() { - // Source generator cannot emit a usable JsonTypeInfo for this invalid polymorphic configuration (build-time SYSLIB diagnostic or generator-side rejection), so the runtime InvalidOperationException is validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Source generator rejects this invalid polymorphic configuration at build time (SYSLIB diagnostic); the runtime InvalidOperationException is validated under reflection only."); + } + // Pattern: NestedDerivedEnclosingMismatch : NestedBase.NestedBox>. // Target: NestedBase.NestedBox>. // The enclosing argument differs (int vs string) so unification MUST fail. The 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 503e71325f33ae..19b791b3c3665e 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.TypeClassifier.cs @@ -1349,8 +1349,6 @@ public async Task Classifier_EmptyObject_MatchesFirstDeclaredCase() #endregion - #region Generation parity (folded from source-gen suite) - [Fact] public void PolymorphismOptions_AreGenerated() { @@ -1431,8 +1429,6 @@ public async Task OpenGenericDerivedType_PartiallyConcrete_RoundTrips() Assert.Equal("hello", d.Extra); } - #endregion - #region Helpers private JsonSerializerOptions CreateOptionsWithStructuralClassifier() diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs index 78275e18c54ced..516371c5693706 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 Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -548,11 +549,14 @@ public void GenericDictionaryOfInterface_WithValidJson_ThrowsNotSupportedExcepti Assert.Throws(() => JsonSerializer.Deserialize("""{"":{}}""")); } - [Fact] + [ConditionalFact] public async Task AnonymousType() { - // Anonymous types are unspeakable and cannot be registered with the source generator; validated under reflection only. - if (Serializer.IsSourceGeneratedSerializer) return; + if (Serializer.IsSourceGeneratedSerializer) + { + throw new SkipTestException("Anonymous types are unspeakable and cannot be registered with the source generator."); + } + const string Expected = """{"x":1,"y":true}"""; var value = new { x = 1, y = true }; From 3e27f25a6afd880116b0ff9e3a930a9668127716 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 24 Jun 2026 13:48:51 +0300 Subject: [PATCH 4/6] Fix JsonValue.Create call in TestMultiContextSerialization for SG The JsonNode context branch was calling JsonValue.Create(value) which internally uses JsonSerializerOptions.Default. In test projects with JsonSerializerIsReflectionEnabledByDefault=false (i.e. the SG tests), that resolver is EmptyJsonTypeInfoResolver and fails for any non-primitive TValue. This latent bug was exposed by moving the polymorphic tests into Common, where they now run under source-gen wrappers. Resolve the TypeInfo via the wrapper (which uses the SG context's options when available) and pass it explicitly to JsonValue.Create. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Text.Json/tests/Common/SerializerTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs index 59b8541318b57f..9839bae67fd2a7 100644 --- a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json.Nodes; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; @@ -196,7 +197,8 @@ protected async Task TestMultiContextSerialization( if (contexts.HasFlag(SerializedValueContext.JsonNode)) { const string key = "key"; - var jsonObject = new JsonObject { [key] = JsonValue.Create(value) }; + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(options); + var jsonObject = new JsonObject { [key] = JsonValue.Create(value, typeInfo) }; if (expectedExceptionType != null) { From edfa62b4a60ba3e82f8798048b893fab5891899c Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 24 Jun 2026 17:08:29 +0300 Subject: [PATCH 5/6] Fix indentation of nested JsonSerializerContext declarations in SG PolymorphicTests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Serialization/PolymorphicTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 118bb2421ec48b..c45e7748319014 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 @@ -237,7 +237,7 @@ public PolymorphicTests_Metadata() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] - internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext + internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext { } } @@ -478,7 +478,7 @@ public PolymorphicTests_Default() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] - internal sealed partial class PolymorphicTestsContext_Default : JsonSerializerContext + internal sealed partial class PolymorphicTestsContext_Default : JsonSerializerContext { } } From a059aef4189a1fad4f5e6a55b9fe631500eabfaa Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 24 Jun 2026 18:24:07 +0300 Subject: [PATCH 6/6] Fix source-gen polymorphism tests under reflection-disabled config Route migrated static JsonSerializer.* calls through the Serializer wrapper abstraction so the source-gen resolver is injected, convert the affected [Fact] void tests to async Task, and register the newly needed types (UsaCustomer, MyClass, MyThingCollection, MyThingDictionary) in both source-gen contexts. ReadPrimitivesFail now uses ThrowsAnyAsync since the Element/Document/Node round-trip wrappers surface the internal JsonReaderException subclass. Fixes 276 source-gen CI failures that threw InvalidOperationException (reflection disabled). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/Common/PolymorphicTests.cs | 58 +++++++++---------- .../tests/Common/SerializerTests.cs | 6 +- .../Serialization/PolymorphicTests.cs | 8 +++ 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs index 516371c5693706..06aa89d9332f95 100644 --- a/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PolymorphicTests.cs @@ -40,16 +40,16 @@ public async Task PrimitivesAsRootObject(object? value, string expectedJson) } [Fact] - public void ReadPrimitivesFail() + public async Task ReadPrimitivesFail() { - Assert.Throws(() => JsonSerializer.Deserialize(@"")); - Assert.Throws(() => JsonSerializer.Deserialize(@"a")); + await Assert.ThrowsAnyAsync(() => Serializer.DeserializeWrapper(@"")); + await Assert.ThrowsAnyAsync(() => Serializer.DeserializeWrapper(@"a")); } [Fact] - public void ParseUntyped() + public async Task ParseUntyped() { - object obj = JsonSerializer.Deserialize(""" + object obj = await Serializer.DeserializeWrapper(""" "hello" """); Assert.IsType(obj); @@ -57,15 +57,15 @@ public void ParseUntyped() Assert.Equal(JsonValueKind.String, element.ValueKind); Assert.Equal("hello", element.GetString()); - obj = JsonSerializer.Deserialize(@"true"); + obj = await Serializer.DeserializeWrapper(@"true"); element = (JsonElement)obj; Assert.Equal(JsonValueKind.True, element.ValueKind); Assert.True(element.GetBoolean()); - obj = JsonSerializer.Deserialize(@"null"); + obj = await Serializer.DeserializeWrapper(@"null"); Assert.Null(obj); - obj = JsonSerializer.Deserialize("[]"); + obj = await Serializer.DeserializeWrapper("[]"); element = (JsonElement)obj; Assert.Equal(JsonValueKind.Array, element.ValueKind); } @@ -453,7 +453,7 @@ public async Task StaticAnalysisBaseline() customer.Verify(); string json = await Serializer.SerializeWrapper(customer); - Customer deserializedCustomer = JsonSerializer.Deserialize(json); + Customer deserializedCustomer = await Serializer.DeserializeWrapper(json); deserializedCustomer.Verify(); } @@ -469,7 +469,7 @@ public async Task StaticAnalysis() // Generic inference used = string json = await Serializer.SerializeWrapper(person); - Customer deserializedCustomer = JsonSerializer.Deserialize(json); + Customer deserializedCustomer = await Serializer.DeserializeWrapper(json); // We only serialized the Person base class, so the Customer fields should be default. Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); @@ -488,7 +488,7 @@ public async Task WriteStringWithRuntimeType() string json = await Serializer.SerializeWrapper(person, person.GetType()); - Customer deserializedCustomer = JsonSerializer.Deserialize(json); + Customer deserializedCustomer = await Serializer.DeserializeWrapper(json); // We serialized the Customer Assert.Equal(typeof(Customer), deserializedCustomer.GetType()); @@ -510,7 +510,7 @@ public async Task StaticAnalysisWithRelationship() // Generic inference used = string json = await Serializer.SerializeWrapper(customer); - UsaCustomer deserializedCustomer = JsonSerializer.Deserialize(json); + UsaCustomer deserializedCustomer = await Serializer.DeserializeWrapper(json); // We only serialized the Customer base class Assert.Equal(typeof(UsaCustomer), deserializedCustomer.GetType()); @@ -519,34 +519,34 @@ public async Task StaticAnalysisWithRelationship() } [Fact] - public void PolymorphicInterface_NotSupported() + public async Task PolymorphicInterface_NotSupported() { - Assert.Throws(() => JsonSerializer.Deserialize("""{ "Value": "A value", "Thing": { "Number": 123 } }""")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("""{ "Value": "A value", "Thing": { "Number": 123 } }""")); } [Fact] - public void GenericListOfInterface_WithInvalidJson_ThrowsJsonException() + public async Task GenericListOfInterface_WithInvalidJson_ThrowsJsonException() { - Assert.Throws(() => JsonSerializer.Deserialize("false")); - Assert.Throws(() => JsonSerializer.Deserialize("{}")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("false")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); } [Fact] - public void GenericListOfInterface_WithValidJson_ThrowsNotSupportedException() + public async Task GenericListOfInterface_WithValidJson_ThrowsNotSupportedException() { - Assert.Throws(() => JsonSerializer.Deserialize("[{}]")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("[{}]")); } [Fact] - public void GenericDictionaryOfInterface_WithInvalidJson_ThrowsJsonException() + public async Task GenericDictionaryOfInterface_WithInvalidJson_ThrowsJsonException() { - Assert.Throws(() => JsonSerializer.Deserialize("""{"":1}""")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("""{"":1}""")); } [Fact] - public void GenericDictionaryOfInterface_WithValidJson_ThrowsNotSupportedException() + public async Task GenericDictionaryOfInterface_WithValidJson_ThrowsNotSupportedException() { - Assert.Throws(() => JsonSerializer.Deserialize("""{"":{}}""")); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("""{"":{}}""")); } [ConditionalFact] @@ -600,29 +600,29 @@ public async Task CustomResolverWithFailingAncestorType_DoesNotSurfaceException( Assert.Equal("[1,2,3]", json); } - class MyClass + internal class MyClass { public string Value { get; set; } public IThing Thing { get; set; } } - interface IThing + internal interface IThing { int Number { get; set; } } - class MyThing : IThing + internal class MyThing : IThing { public int Number { get; set; } } - class MyDerivedThing : MyThing + internal class MyDerivedThing : MyThing { } - class MyThingCollection : List { } + internal class MyThingCollection : List { } - class MyThingDictionary : Dictionary { } + internal class MyThingDictionary : Dictionary { } [Theory] [InlineData(typeof(PolymorphicTypeWithConflictingPropertyNameAtBase), typeof(PolymorphicTypeWithConflictingPropertyNameAtBase.Derived))] diff --git a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs index 9839bae67fd2a7..084a88aa32b6ed 100644 --- a/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/SerializerTests.cs @@ -330,13 +330,15 @@ await Assert.ThrowsAsync(expectedExceptionType, async () => { JsonNode jsonNode = await Serializer.DeserializeWrapper(wrappedJson, options); - JsonSerializer.Deserialize(jsonNode[key], options); + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(options); + JsonSerializer.Deserialize(jsonNode[key], typeInfo); }); } else { JsonNode jsonNode = await Serializer.DeserializeWrapper(wrappedJson, options); - TValue value = JsonSerializer.Deserialize(jsonNode[key], options); + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(options); + TValue value = JsonSerializer.Deserialize(jsonNode[key], typeInfo); Assert.Equal(expectedValue, value, equalityComparer); } } 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 c45e7748319014..78c2aa445ce340 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 @@ -237,6 +237,10 @@ public PolymorphicTests_Metadata() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] + [JsonSerializable(typeof(UsaCustomer))] + [JsonSerializable(typeof(PolymorphicTests.MyClass))] + [JsonSerializable(typeof(PolymorphicTests.MyThingCollection))] + [JsonSerializable(typeof(PolymorphicTests.MyThingDictionary))] internal sealed partial class PolymorphicTestsContext_Metadata : JsonSerializerContext { } @@ -478,6 +482,10 @@ public PolymorphicTests_Default() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(OpenGenericBase_DuplicateDerivedRegistrations))] + [JsonSerializable(typeof(UsaCustomer))] + [JsonSerializable(typeof(PolymorphicTests.MyClass))] + [JsonSerializable(typeof(PolymorphicTests.MyThingCollection))] + [JsonSerializable(typeof(PolymorphicTests.MyThingDictionary))] internal sealed partial class PolymorphicTestsContext_Default : JsonSerializerContext { }