diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs index 8f542612b90e4e..5fad7fd1e9a791 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs @@ -148,6 +148,13 @@ internal bool TryHandleSerializedObjectReference(Utf8JsonWriter writer, object v if (resolver.ContainsReferenceForCycleDetection(value)) { writer.WriteNullValue(); + + if (polymorphicConverter is not null) + { + // Clear out any polymorphic state. + state.PolymorphicTypeDiscriminator = null; + state.PolymorphicTypeResolver = null; + } return true; } diff --git a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs index 0affb96da401e4..fd092427890a00 100644 --- a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs +++ b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.IgnoreCycles.cs @@ -429,6 +429,48 @@ public async Task IgnoreCycles_BoxedValueShouldNotBeIgnored() await Test_Serialize_And_SerializeAsync_Contains(root, expectedSubstring: @"""DayOfBirth"":15", expectedTimes: 2, s_optionsIgnoreCycles); } + [Fact] + public async Task IgnoreCycles_DerivedType_InArray() + { + var worker = new OfficeWorker + { + Office = new Office + { + Dummy = new() + } + }; + + worker.Office.Staff = [worker, new RemoteWorker()]; + + await Test_Serialize_And_SerializeAsync(worker, """{"Office":{"Staff":[null,{"$type":"remote"}],"Dummy":{}}}""", s_optionsIgnoreCycles); + + worker.Office.Staff = [worker]; + + await Test_Serialize_And_SerializeAsync(worker, """{"Office":{"Staff":[null],"Dummy":{}}}""", s_optionsIgnoreCycles); + } + + [JsonDerivedType(typeof(OfficeWorker), "office")] + [JsonDerivedType(typeof(RemoteWorker), "remote")] + public abstract class EmployeeLocation + { + } + + public class OfficeWorker : EmployeeLocation + { + public Office Office { get; set; } + } + + public class RemoteWorker : EmployeeLocation + { + } + + public class Office + { + public EmployeeLocation[] Staff { get; set; } + + public EmptyClass Dummy { get; set; } + } + [Fact] public async Task CycleDetectionStatePersistsAcrossContinuations() { diff --git a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs index dd166409de2699..8cc7cb1084f4b7 100644 --- a/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs +++ b/src/libraries/System.Text.Json/tests/Common/ReferenceHandlerTests/ReferenceHandlerTests.Serialize.cs @@ -224,5 +224,49 @@ public async Task CustomHashCode() // otherwise objects would not be correctly identified when searching for them in the dictionary. Assert.Same(listCopy[0], listCopy[1]); } + + [Fact] + public async Task Preserve_DerivedType_InArray() + { + var worker = new OfficeWorker + { + Office = new Office + { + Dummy = new() + } + }; + + worker.Office.Staff = [worker, new RemoteWorker()]; + + string json = await Serializer.SerializeWrapper(worker, s_serializerOptionsPreserve); + Assert.Equal("""{"$id":"1","Office":{"$id":"2","Staff":[{"$ref":"1"},{"$id":"3","$type":"remote"}],"Dummy":{"$id":"4"}}}""", json); + + worker.Office.Staff = [worker]; + + json = await Serializer.SerializeWrapper(worker, s_serializerOptionsPreserve); + Assert.Equal("""{"$id":"1","Office":{"$id":"2","Staff":[{"$ref":"1"}],"Dummy":{"$id":"3"}}}""", json); + } + + [JsonDerivedType(typeof(OfficeWorker), "office")] + [JsonDerivedType(typeof(RemoteWorker), "remote")] + public abstract class EmployeeLocation + { + } + + public class OfficeWorker : EmployeeLocation + { + public Office Office { get; set; } + } + + public class RemoteWorker : EmployeeLocation + { + } + + public class Office + { + public EmployeeLocation[] Staff { get; set; } + + public EmptyClass Dummy { get; set; } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs index 8c073e1e5e30ee..3f9f56a26340ab 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs @@ -89,6 +89,11 @@ public ReferenceHandlerTests_IgnoreCycles_Metadata(JsonSerializerWrapper seriali [JsonSerializable(typeof(TreeNode>))] [JsonSerializable(typeof(TreeNode))] [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(EmployeeLocation))] + [JsonSerializable(typeof(EmployeeLocation[]))] + [JsonSerializable(typeof(OfficeWorker))] + [JsonSerializable(typeof(Office))] + [JsonSerializable(typeof(RemoteWorker))] internal sealed partial class ReferenceHandlerTests_IgnoreCyclesContext_Metadata : JsonSerializerContext { } @@ -172,6 +177,11 @@ public ReferenceHandlerTests_IgnoreCycles_Default(JsonSerializerWrapper serializ [JsonSerializable(typeof(TreeNode>))] [JsonSerializable(typeof(TreeNode))] [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(EmployeeLocation))] + [JsonSerializable(typeof(EmployeeLocation[]))] + [JsonSerializable(typeof(OfficeWorker))] + [JsonSerializable(typeof(Office))] + [JsonSerializable(typeof(RemoteWorker))] internal sealed partial class ReferenceHandlerTests_IgnoreCyclesContext_Default : JsonSerializerContext { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs index 03c1ede64586f0..36cb8c42f4efda 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs @@ -139,6 +139,11 @@ public ReferenceHandlerTests_Metadata(JsonSerializerWrapper serializer) [JsonSerializable(typeof(ClassWithConflictingIdProperty))] [JsonSerializable(typeof(ClassWithIgnoredConflictingProperty))] [JsonSerializable(typeof(ClassWithExtensionDataConflictingProperty))] + [JsonSerializable(typeof(EmployeeLocation))] + [JsonSerializable(typeof(EmployeeLocation[]))] + [JsonSerializable(typeof(OfficeWorker))] + [JsonSerializable(typeof(Office))] + [JsonSerializable(typeof(RemoteWorker))] internal sealed partial class ReferenceHandlerTestsContext_Metadata : JsonSerializerContext { } @@ -281,6 +286,11 @@ public ReferenceHandlerTests_Default(JsonSerializerWrapper serializer) [JsonSerializable(typeof(ClassWithConflictingIdProperty))] [JsonSerializable(typeof(ClassWithIgnoredConflictingProperty))] [JsonSerializable(typeof(ClassWithExtensionDataConflictingProperty))] + [JsonSerializable(typeof(EmployeeLocation))] + [JsonSerializable(typeof(EmployeeLocation[]))] + [JsonSerializable(typeof(OfficeWorker))] + [JsonSerializable(typeof(Office))] + [JsonSerializable(typeof(RemoteWorker))] internal sealed partial class ReferenceHandlerTestsContext_Default : JsonSerializerContext { }