diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index 4a2be44c8629c..89b9cdad1eeff 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -312,7 +312,9 @@ internal sealed override bool OnTryWrite( if (!jsonPropertyInfo.GetMemberAndWriteJson(objectValue!, ref state, writer)) { - Debug.Assert(jsonPropertyInfo.ConverterBase.ConverterStrategy != ConverterStrategy.Value); + Debug.Assert(jsonPropertyInfo.ConverterBase.ConverterStrategy != ConverterStrategy.Value || + jsonPropertyInfo.ConverterBase.TypeToConvert == JsonTypeInfo.ObjectType); + return false; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs index 6ea73dd49c9ec..a17f570263c90 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs @@ -476,6 +476,7 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json // Ignore the naming policy for extension data. state.Current.IgnoreDictionaryKeyPolicy = true; + state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; success = dictionaryConverter.OnWriteResume(writer, value, options, ref state); if (success) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs index 71c30c512756c..db52ffc079ffe 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs @@ -125,9 +125,6 @@ private static string WriteUsingMetadata(in TValue value, JsonTypeInfo? throw new ArgumentNullException(nameof(jsonTypeInfo)); } - WriteStack state = default; - state.Initialize(jsonTypeInfo, supportContinuation: false); - JsonSerializerOptions options = jsonTypeInfo.Options; using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index 2f0347f68332a..06d4a3fec45e2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -258,8 +258,7 @@ static void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame) if (frame.JsonTypeInfo != null && frame.IsProcessingEnumerable()) { - IEnumerable? enumerable = (IEnumerable?)frame.ReturnValue; - if (enumerable == null) + if (frame.ReturnValue is not IEnumerable enumerable) { return; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 22f67983ec346..58a5ad972d050 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -253,13 +253,10 @@ public void DisposePendingDisposablesOnException() DisposeFrame(Current.CollectionEnumerator, ref exception); int stackSize = Math.Max(_count, _continuationCount); - if (stackSize > 1) + for (int i = 0; i < stackSize - 1; i++) { - for (int i = 0; i < stackSize - 1; i++) - { - Debug.Assert(_previous[i].AsyncEnumerator is null); - DisposeFrame(_previous[i].CollectionEnumerator, ref exception); - } + Debug.Assert(_previous[i].AsyncEnumerator is null); + DisposeFrame(_previous[i].CollectionEnumerator, ref exception); } if (exception is not null) @@ -294,12 +291,9 @@ public async ValueTask DisposePendingDisposablesOnExceptionAsync() exception = await DisposeFrame(Current.CollectionEnumerator, Current.AsyncEnumerator, exception).ConfigureAwait(false); int stackSize = Math.Max(_count, _continuationCount); - if (stackSize > 1) + for (int i = 0; i < stackSize - 1; i++) { - for (int i = 0; i < stackSize - 1; i++) - { - exception = await DisposeFrame(_previous[i].CollectionEnumerator, _previous[i].AsyncEnumerator, exception).ConfigureAwait(false); - } + exception = await DisposeFrame(_previous[i].CollectionEnumerator, _previous[i].AsyncEnumerator, exception).ConfigureAwait(false); } if (exception is not null) @@ -353,7 +347,7 @@ public string PropertyPath() return sb.ToString(); - void AppendStackFrame(StringBuilder sb, in WriteStackFrame frame) + static void AppendStackFrame(StringBuilder sb, in WriteStackFrame frame) { // Append the property name. string? propertyName = frame.DeclaredJsonPropertyInfo?.MemberInfo?.Name; @@ -366,7 +360,7 @@ void AppendStackFrame(StringBuilder sb, in WriteStackFrame frame) AppendPropertyName(sb, propertyName); } - void AppendPropertyName(StringBuilder sb, string? propertyName) + static void AppendPropertyName(StringBuilder sb, string? propertyName) { if (propertyName != null) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs index 8157f9bbdf3c9..235fa8010deb8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ReferenceHandlerTests.IgnoreCycles.cs @@ -14,7 +14,7 @@ namespace System.Text.Json.Serialization.Tests public class ReferenceHandlerTests_IgnoreCycles { private static readonly JsonSerializerOptions s_optionsIgnoreCycles = - new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles }; + new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles, DefaultBufferSize = 1 }; [Fact] public async Task IgnoreCycles_OnObject() @@ -401,6 +401,25 @@ public async void IgnoreCycles_BoxedValueShouldNotBeIgnored() await Test_Serialize_And_SerializeAsync_Contains(root, expectedSubstring: @"""DayOfBirth"":15", expectedTimes: 2, s_optionsIgnoreCycles); } + [Fact] + public async Task CycleDetectionStatePersistsAcrossContinuations() + { + string expectedValueJson = @"{""LargePropertyName"":""A large-ish string to force continuations"",""Nested"":null}"; + var recVal = new RecursiveValue { LargePropertyName = "A large-ish string to force continuations" }; + recVal.Nested = recVal; + + var value = new List { recVal, recVal }; + string expectedJson = $"[{expectedValueJson},{expectedValueJson}]"; + + await Test_Serialize_And_SerializeAsync(value, expectedJson, s_optionsIgnoreCycles); + } + + public class RecursiveValue + { + public string LargePropertyName { get; set; } + public RecursiveValue? Nested { get; set; } + } + private async Task Test_Serialize_And_SerializeAsync(T obj, string expected, JsonSerializerOptions options) { string json;