diff --git a/Essentials.SerializationProviders.NewtonsoftJson/NewtonsoftJsonSerializationProvider.cs b/Essentials.SerializationProviders.NewtonsoftJson/NewtonsoftJsonSerializationProvider.cs index 3fa67c1..6f342c4 100644 --- a/Essentials.SerializationProviders.NewtonsoftJson/NewtonsoftJsonSerializationProvider.cs +++ b/Essentials.SerializationProviders.NewtonsoftJson/NewtonsoftJsonSerializationProvider.cs @@ -4,6 +4,7 @@ namespace ktsu.Essentials.SerializationProviders.NewtonsoftJson; using ktsu.Essentials; using System; +using System.Globalization; using System.IO; using System.Text; using Newtonsoft.Json; @@ -39,11 +40,17 @@ public bool TrySerialize(object obj, TextWriter writer) try { - // The caller owns the writer, so disposing the JSON writer must not close it. - using DepthLimitedJsonTextWriter jsonWriter = new(writer, MaxDepth) { CloseOutput = false }; - JsonSerializer serializer = JsonSerializer.Create(settings); - serializer.Serialize(jsonWriter, obj); - jsonWriter.Flush(); + // Serialize into a local buffer and copy it to the caller's writer only on success. + // Disposing a JsonTextWriter closes any object or array a failure left open, so + // streaming straight into the caller's writer would leave truncated JSON that parses. + using StringWriter buffer = new(CultureInfo.InvariantCulture); + using (DepthLimitedJsonTextWriter jsonWriter = new(buffer, MaxDepth)) + { + JsonSerializer serializer = JsonSerializer.Create(settings); + serializer.Serialize(jsonWriter, obj); + } + + writer.Write(buffer.ToString()); return true; } catch (JsonSerializationException) diff --git a/Essentials.Tests/NewtonsoftJsonSerializationProviderTests.cs b/Essentials.Tests/NewtonsoftJsonSerializationProviderTests.cs index 0b16e40..ac5972e 100644 --- a/Essentials.Tests/NewtonsoftJsonSerializationProviderTests.cs +++ b/Essentials.Tests/NewtonsoftJsonSerializationProviderTests.cs @@ -30,6 +30,18 @@ public void TrySerialize_WriterThrowingIOException_ReturnsFalse() Assert.IsFalse(provider.TrySerialize(new { A = 1 }, writer)); } + [TestMethod] + public void TrySerialize_CyclicObject_ReturnsFalseAndWritesNothing() + { + NewtonsoftJsonSerializationProvider provider = new(); + Node node = new() { Name = "a" }; + node.Next = node; + using StringWriter writer = new(); + + Assert.IsFalse(provider.TrySerialize(node, writer)); + Assert.AreEqual(string.Empty, writer.ToString()); + } + [TestMethod] [DataRow(64)] [DataRow(65)]