diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index c014669fd41b4b..07091b502b1143 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -587,7 +587,9 @@ public void Flush() { } public System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public void Reset() { } public void Reset(System.Buffers.IBufferWriter bufferWriter) { } + public void Reset(System.Buffers.IBufferWriter bufferWriter, System.Text.Json.JsonWriterOptions options) { } public void Reset(System.IO.Stream utf8Json) { } + public void Reset(System.IO.Stream utf8Json, System.Text.Json.JsonWriterOptions options) { } public void WriteBase64String(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan bytes) { } public void WriteBase64String(System.ReadOnlySpan propertyName, System.ReadOnlySpan bytes) { } public void WriteBase64String(string propertyName, System.ReadOnlySpan bytes) { } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index 6409daf43381d0..d87f5fbc771332 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -289,6 +289,9 @@ public void Reset() /// /// Thrown when the instance of that is passed in is null. /// + /// + /// Thrown when the instance of that is passed in does not support writing. + /// /// /// The instance of has been disposed. /// @@ -297,9 +300,14 @@ public void Reset(Stream utf8Json) CheckNotDisposed(); if (utf8Json == null) + { throw new ArgumentNullException(nameof(utf8Json)); + } + if (!utf8Json.CanWrite) + { throw new ArgumentException(SR.StreamNotWritable); + } _stream = utf8Json; if (_arrayBufferWriter == null) @@ -310,11 +318,32 @@ public void Reset(Stream utf8Json) { _arrayBufferWriter.Clear(); } - _output = null; + _output = null; ResetHelper(); } + /// + /// Resets the internal state so that it can be re-used with the new instance of + /// and the specified . + /// + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the . + /// + /// Thrown when the instance of that is passed in is null. + /// + /// + /// Thrown when the instance of that is passed in does not support writing. + /// + /// + /// The instance of has been disposed. + /// + public void Reset(Stream utf8Json, JsonWriterOptions options) + { + Reset(utf8Json); + SetOptions(options); + } + /// /// Resets the internal state so that it can be re-used with the new instance of . /// @@ -340,6 +369,24 @@ public void Reset(IBufferWriter bufferWriter) ResetHelper(); } + /// + /// Resets the internal state so that it can be re-used with the new instance of + /// and the specified . + /// + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the . + /// + /// Thrown when the instance of that is passed in is null. + /// + /// + /// The instance of has been disposed. + /// + public void Reset(IBufferWriter bufferWriter, JsonWriterOptions options) + { + Reset(bufferWriter); + SetOptions(options); + } + internal void ResetAllStateForCacheReuse() { ResetHelper(); @@ -349,7 +396,7 @@ internal void ResetAllStateForCacheReuse() _output = null; } - internal void Reset(IBufferWriter bufferWriter, JsonWriterOptions options) + internal void ConfigureForCacheReuse(IBufferWriter bufferWriter, JsonWriterOptions options) { Debug.Assert(_output is null && _stream is null && _arrayBufferWriter is null); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriterCache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriterCache.cs index bfe92487f01c8f..47a6f46d7be2b6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriterCache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriterCache.cs @@ -29,7 +29,7 @@ public static Utf8JsonWriter RentWriterAndBuffer(JsonWriterOptions options, int writer = state.Writer; bufferWriter.InitializeEmptyInstance(defaultBufferSize); - writer.Reset(bufferWriter, options); + writer.ConfigureForCacheReuse(bufferWriter, options); } else { @@ -50,7 +50,7 @@ public static Utf8JsonWriter RentWriter(JsonSerializerOptions options, IBufferWr { // First JsonSerializer call in the stack -- initialize & return the cached instance. writer = state.Writer; - writer.Reset(bufferWriter, options.GetWriterOptions()); + writer.ConfigureForCacheReuse(bufferWriter, options.GetWriterOptions()); } else { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs index 8d716b3ba089c0..b545496857b71d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs @@ -1180,18 +1180,190 @@ public void InvalidReset(JsonWriterOptions options) Assert.Throws(() => writeToStream.Reset((Stream)null)); Assert.Throws(() => writeToStream.Reset((IBufferWriter)null)); + Assert.Throws(() => writeToStream.Reset((Stream)null, options)); + Assert.Throws(() => writeToStream.Reset((IBufferWriter)null, options)); stream.Dispose(); Assert.Throws(() => writeToStream.Reset(stream)); + Assert.Throws(() => writeToStream.Reset(stream, options)); var output = new FixedSizedBufferWriter(256); using var writeToIBW = new Utf8JsonWriter(output, options); Assert.Throws(() => writeToIBW.Reset((Stream)null)); Assert.Throws(() => writeToIBW.Reset((IBufferWriter)null)); + Assert.Throws(() => writeToIBW.Reset((Stream)null, options)); + Assert.Throws(() => writeToIBW.Reset((IBufferWriter)null, options)); Assert.Throws(() => writeToIBW.Reset(stream)); + Assert.Throws(() => writeToIBW.Reset(stream, options)); + } + + [Fact] + public static void JsonSerializerCacheNotPoisonedByDisposedWriter() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new DisposingInt32Converter()); + + Assert.Throws(() => JsonSerializer.Serialize(1, options)); + Assert.Equal("2", JsonSerializer.Serialize(2)); + } + + [Theory] + [MemberData(nameof(JsonOptions_TestData))] + public void ResetWithNewOptions(JsonWriterOptions options) + { + var newOptions = new JsonWriterOptions + { + Indented = !options.Indented, + SkipValidation = !options.SkipValidation, + MaxDepth = 500, + IndentCharacter = '\t', + IndentSize = 4, + }; + + var stream = new MemoryStream(); + using var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + writeToStream.Reset(stream, newOptions); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(newOptions.Indented, writeToStream.Options.Indented); + Assert.Equal(newOptions.SkipValidation, writeToStream.Options.SkipValidation); + Assert.Equal(500, writeToStream.Options.MaxDepth); + Assert.Equal('\t', writeToStream.Options.IndentCharacter); + Assert.Equal(4, writeToStream.Options.IndentSize); + + long previousWritten = stream.Position; + writeToStream.Flush(); + Assert.Equal(previousWritten, stream.Position); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.NotEqual(previousWritten, stream.Position); + + var output = new FixedSizedBufferWriter(257); + using var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + writeToIBW.Reset(output, newOptions); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(newOptions.Indented, writeToIBW.Options.Indented); + Assert.Equal(newOptions.SkipValidation, writeToIBW.Options.SkipValidation); + Assert.Equal(500, writeToIBW.Options.MaxDepth); + Assert.Equal('\t', writeToIBW.Options.IndentCharacter); + Assert.Equal(4, writeToIBW.Options.IndentSize); + + previousWritten = output.FormattedCount; + writeToIBW.Flush(); + Assert.Equal(previousWritten, output.FormattedCount); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.NotEqual(previousWritten, output.FormattedCount); + } + + private sealed class DisposingInt32Converter : System.Text.Json.Serialization.JsonConverter + { + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + throw new NotSupportedException(); + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + { + writer.Dispose(); + throw new InvalidOperationException(); + } + } + + [Theory] + [MemberData(nameof(JsonOptions_TestData))] + public void ResetChangeOutputModeWithNewOptions(JsonWriterOptions options) + { + var newOptions = new JsonWriterOptions + { + Indented = !options.Indented, + SkipValidation = !options.SkipValidation, + }; + + var stream = new MemoryStream(); + using var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + var output = new FixedSizedBufferWriter(256); + writeToStream.Reset(output, newOptions); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(newOptions.Indented, writeToStream.Options.Indented); + Assert.Equal(newOptions.SkipValidation, writeToStream.Options.SkipValidation); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + Assert.True(writeToStream.BytesCommitted != 0); + Assert.True(output.FormattedCount != 0); + + output = new FixedSizedBufferWriter(256); + using var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + stream = new MemoryStream(); + writeToIBW.Reset(stream, newOptions); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(newOptions.Indented, writeToIBW.Options.Indented); + Assert.Equal(newOptions.SkipValidation, writeToIBW.Options.SkipValidation); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + Assert.True(writeToIBW.BytesCommitted != 0); + Assert.True(stream.Position != 0); + } + + [Fact] + public void ResetWithNewOptions_ProducesExpectedOutput() + { + var output = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(output, new JsonWriterOptions { Indented = false }); + + writer.WriteStartObject(); + writer.WriteNumber("x", 1); + writer.WriteEndObject(); + writer.Flush(); + + string compact = Encoding.UTF8.GetString(output.WrittenSpan.ToArray()); + Assert.Equal("""{"x":1}""", compact); + + output.Clear(); + writer.Reset(output, new JsonWriterOptions { Indented = true }); + + writer.WriteStartObject(); + writer.WriteNumber("x", 1); + writer.WriteEndObject(); + writer.Flush(); + + string indented = Encoding.UTF8.GetString(output.WrittenSpan.ToArray()); + Assert.Contains("\n", indented); + Assert.Contains("\"x\": 1", indented); } [Theory] @@ -1381,6 +1553,7 @@ public void UseAfterDisposeInvalid(JsonWriterOptions options) var stream = new MemoryStream(); Assert.Throws(() => jsonUtf8.Reset(stream)); + Assert.Throws(() => jsonUtf8.Reset(stream, options)); using var writeToStream = new Utf8JsonWriter(stream, options); writeToStream.WriteStartObject(); @@ -1398,6 +1571,7 @@ public void UseAfterDisposeInvalid(JsonWriterOptions options) Assert.Throws(() => writeToStream.Reset()); Assert.Throws(() => jsonUtf8.Reset(output)); + Assert.Throws(() => jsonUtf8.Reset(output, options)); } [Theory] @@ -1423,6 +1597,7 @@ public async Task UseAfterDisposeInvalidAsync(JsonWriterOptions options) var stream = new MemoryStream(); Assert.Throws(() => jsonUtf8.Reset(stream)); + Assert.Throws(() => jsonUtf8.Reset(stream, options)); using var writeToStream = new Utf8JsonWriter(stream, options); writeToStream.WriteStartObject(); @@ -1440,6 +1615,7 @@ public async Task UseAfterDisposeInvalidAsync(JsonWriterOptions options) Assert.Throws(() => writeToStream.Reset()); Assert.Throws(() => jsonUtf8.Reset(output)); + Assert.Throws(() => jsonUtf8.Reset(output, options)); } [Theory]