Re-design Utf8JsonWriter as a regular class rather than a ref struct. (dotnet/corefx...
authorAhson Khan <ahkha@microsoft.com>
Thu, 18 Apr 2019 06:57:50 +0000 (23:57 -0700)
committerGitHub <noreply@github.com>
Thu, 18 Apr 2019 06:57:50 +0000 (23:57 -0700)
* Add an in-box array-backed IBufferWriter<T>

* Update Utf8JsonWriter ref and main writer file.

* Fix JsonWriter WriteValue APIs.

* Use Environment.NewLine static and invert some stream conditions.

* Update JsonWriter properties and fix serializer build breaks.

* Update JsonWriter unit tests.

* Add xml comments, clean up, and improve test coverage.

* Update JsonDocument and JsonSerializer to react to JsonWriter changes.

* Normalize the reference assembly.

* Do not escape/validate comments and update issue number.

* Do not escape comments and validate they don't contain embedded
delimiter

* Remove dead code and update issue number in comments.

* Throw InvalidOEx instead of ArgEx if IBW doesn't give requested memory.

* Fix test build breaks for netfx.

* Remove dead code and fix source packaging.

* Address PR feedback.

* Disable writing floats test on windows

* 8 digit floats don't work well on older TFMs. Reduce to 7.

Commit migrated from https://github.com/dotnet/corefx/commit/1f9b84a0804e868c7e0f37a3c10fbaf7c735ae14

83 files changed:
src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs [new file with mode: 0644]
src/libraries/System.Memory/ref/System.Memory.cs
src/libraries/System.Memory/src/Resources/Strings.resx
src/libraries/System.Memory/src/System.Memory.csproj
src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs [new file with mode: 0644]
src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs [new file with mode: 0644]
src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs [new file with mode: 0644]
src/libraries/System.Memory/tests/System.Memory.Tests.csproj
src/libraries/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj
src/libraries/System.Text.Json/ref/System.Text.Json.Manual.cs [new file with mode: 0644]
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/ref/System.Text.Json.csproj
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs with 93% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs
src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs [deleted file]
src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs
src/libraries/System.Text.Json/tests/JsonDocumentTests.cs
src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs
src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs [deleted file]
src/libraries/System.Text.Json/tests/ResizableArray.cs [deleted file]
src/libraries/System.Text.Json/tests/Resources/Strings.resx
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs

diff --git a/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs b/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs
new file mode 100644 (file)
index 0000000..193eab8
--- /dev/null
@@ -0,0 +1,185 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Buffers
+{
+    /// <summary>
+    /// Represents a heap-based, array-backed output sink into which <typeparam name="T"/> data can be written.
+    /// </summary>
+#if USE_ABW_INTERNALLY
+    internal
+#else
+    public
+#endif
+    sealed class ArrayBufferWriter<T> : IBufferWriter<T>
+    {
+        private T[] _buffer;
+        private int _index;
+
+        private const int MinimumBufferSize = 256;
+
+        /// <summary>
+        /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
+        /// with the default initial capacity.
+        /// </summary>
+        public ArrayBufferWriter()
+        {
+            _buffer = new T[MinimumBufferSize];
+            _index = 0;
+        }
+
+        /// <summary>
+        /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
+        /// with an initial capacity specified.
+        /// </summary>
+        /// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
+        /// <exception cref="ArgumentException">
+        /// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
+        /// </exception>
+        public ArrayBufferWriter(int initialCapacity)
+        {
+            if (initialCapacity <= 0)
+                throw new ArgumentException(nameof(initialCapacity));
+
+            _buffer = new T[initialCapacity];
+            _index = 0;
+        }
+
+        /// <summary>
+        /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
+        /// </summary>
+        public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index);
+
+        /// <summary>
+        /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
+        /// </summary>
+        public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index);
+
+        /// <summary>
+        /// Returns the amount of data written to the underlying buffer so far.
+        /// </summary>
+        public int WrittenCount => _index;
+
+        /// <summary>
+        /// Returns the total amount of space within the underlying buffer.
+        /// </summary>
+        public int Capacity => _buffer.Length;
+
+        /// <summary>
+        /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
+        /// </summary>
+        public int FreeCapacity => _buffer.Length - _index;
+
+        /// <summary>
+        /// Clears the data written to the underlying buffer.
+        /// </summary>
+        /// <remarks>
+        /// You must clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it.
+        /// </remarks>
+        public void Clear()
+        {
+            Debug.Assert(_buffer.Length >= _index);
+            _buffer.AsSpan(0, _index).Clear();
+            _index = 0;
+        }
+
+        /// <summary>
+        /// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/>
+        /// </summary>
+        /// <exception cref="ArgumentException">
+        /// Thrown when <paramref name="count"/> is negative.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown when attempting to advance past the end of the underlying buffer.
+        /// </exception>
+        /// <remarks>
+        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
+        /// </remarks>
+        public void Advance(int count)
+        {
+            if (count < 0)
+                throw new ArgumentException(nameof(count));
+
+            if (_index > _buffer.Length - count)
+                ThrowInvalidOperationException(_buffer.Length);
+
+            _index += count;
+        }
+
+        /// <summary>
+        /// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
+        /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
+        /// </summary>
+        /// <exception cref="ArgumentException">
+        /// Thrown when <paramref name="sizeHint"/> is negative.
+        /// </exception>
+        /// <remarks>
+        /// This will never return an empty <see cref="Memory{T}"/>.
+        /// </remarks>
+        /// <remarks>
+        /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
+        /// </remarks>
+        /// <remarks>
+        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
+        /// </remarks>
+        public Memory<T> GetMemory(int sizeHint = 0)
+        {
+            CheckAndResizeBuffer(sizeHint);
+            Debug.Assert(_buffer.Length > _index);
+            return _buffer.AsMemory(_index);
+        }
+
+        /// <summary>
+        /// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
+        /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
+        /// </summary>
+        /// <exception cref="ArgumentException">
+        /// Thrown when <paramref name="sizeHint"/> is negative.
+        /// </exception>
+        /// <remarks>
+        /// This will never return an empty <see cref="Span{T}"/>.
+        /// </remarks>
+        /// <remarks>
+        /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
+        /// </remarks>
+        /// <remarks>
+        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
+        /// </remarks>
+        public Span<T> GetSpan(int sizeHint = 0)
+        {
+            CheckAndResizeBuffer(sizeHint);
+            Debug.Assert(_buffer.Length > _index);
+            return _buffer.AsSpan(_index);
+        }
+
+        private void CheckAndResizeBuffer(int sizeHint)
+        {
+            if (sizeHint < 0)
+                throw new ArgumentException(nameof(sizeHint));
+
+            if (sizeHint == 0)
+            {
+                sizeHint = MinimumBufferSize;
+            }
+
+            if (sizeHint > FreeCapacity)
+            {
+                int growBy = Math.Max(sizeHint, _buffer.Length);
+
+                int newSize = checked(_buffer.Length + growBy);
+
+                Array.Resize(ref _buffer, newSize);
+            }
+
+            Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
+        }
+
+        private static void ThrowInvalidOperationException(int capacity)
+        {
+            throw new InvalidOperationException(SR.Format(SR.BufferWriterAdvancedTooFar, capacity));
+        }
+    }
+}
index 387aa73..111a532 100644 (file)
@@ -153,6 +153,20 @@ namespace System
 }
 namespace System.Buffers
 {
+    public sealed partial class ArrayBufferWriter<T> : System.Buffers.IBufferWriter<T>
+    {
+        public ArrayBufferWriter() { }
+        public ArrayBufferWriter(int initialCapacity) { }
+        public int Capacity { get { throw null; } }
+        public int FreeCapacity { get { throw null; } }
+        public int WrittenCount { get { throw null; } }
+        public System.ReadOnlyMemory<T> WrittenMemory { get { throw null; } }
+        public System.ReadOnlySpan<T> WrittenSpan { get { throw null; } }
+        public void Advance(int count) { }
+        public void Clear() { }
+        public System.Memory<T> GetMemory(int sizeHint = 0) { throw null; }
+        public System.Span<T> GetSpan(int sizeHint = 0) { throw null; }
+    }
     public static partial class BuffersExtensions
     {
         public static void CopyTo<T>(this in System.Buffers.ReadOnlySequence<T> source, System.Span<T> destination) { }
index 31ac00d..1ac9cac 100644 (file)
   <data name="UnexpectedSegmentType" xml:space="preserve">
     <value>Unexpected segment type.</value>
   </data>
+  <data name="BufferWriterAdvancedTooFar" xml:space="preserve">
+    <value>Cannot advance past the end of the buffer, which has a size of {0}.</value>
+  </data>
 </root>
index 685b2de..4afcbc5 100644 (file)
@@ -33,6 +33,9 @@
     <Compile Include="$(CommonPath)\CoreLib\System\Numerics\Hashing\HashHelpers.cs">
       <Link>Common\System\Collections\HashHelpers.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Buffers\ArrayBufferWriter.cs">
+      <Link>Common\System\Buffers\ArrayBufferWriter.cs</Link>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <ReferenceFromRuntime Include="System.Private.CoreLib" />
diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs
new file mode 100644 (file)
index 0000000..b6c730f
--- /dev/null
@@ -0,0 +1,367 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Buffers.Tests
+{
+    public static partial class ArrayBufferWriterTests_Byte
+    {
+        [Fact]
+        public static void ArrayBufferWriter_Ctor()
+        {
+            {
+                var output = new ArrayBufferWriter<byte>();
+                Assert.True(output.FreeCapacity > 0);
+                Assert.True(output.Capacity > 0);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>(200);
+                Assert.True(output.FreeCapacity >= 200);
+                Assert.True(output.Capacity >= 200);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                ArrayBufferWriter<byte> output = default;
+                Assert.Equal(null, output);
+            }
+        }
+
+        [Fact]
+        public static void Invalid_Ctor()
+        {
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<byte>(0));
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<byte>(-1));
+            Assert.Throws<OutOfMemoryException>(() => new ArrayBufferWriter<byte>(int.MaxValue));
+        }
+
+        [Fact]
+        public static void Clear()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            int previousAvailable = output.FreeCapacity;
+            WriteData(output, 2);
+            Assert.True(output.FreeCapacity < previousAvailable);
+            Assert.True(output.WrittenCount > 0);
+            Assert.False(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.False(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            output.Clear();
+            Assert.Equal(0, output.WrittenCount);
+            Assert.True(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.True(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+        }
+
+        [Fact]
+        public static void Advance()
+        {
+            {
+                var output = new ArrayBufferWriter<byte>();
+                int capacity = output.Capacity;
+                Assert.Equal(capacity, output.FreeCapacity);
+                output.Advance(output.FreeCapacity);
+                Assert.Equal(capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>();
+                output.Advance(output.Capacity);
+                Assert.Equal(output.Capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+                int previousCapacity = output.Capacity;
+                Span<byte> _ = output.GetSpan();
+                Assert.True(output.Capacity > previousCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>();
+                WriteData(output, 2);
+                ReadOnlyMemory<byte> previousMemory = output.WrittenMemory;
+                ReadOnlySpan<byte> previousSpan = output.WrittenSpan;
+                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+                output.Advance(10);
+                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
+                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            }
+        }
+
+        [Fact]
+        public static void AdvanceZero()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            WriteData(output, 2);
+            Assert.Equal(2, output.WrittenCount);
+            ReadOnlyMemory<byte> previousMemory = output.WrittenMemory;
+            ReadOnlySpan<byte> previousSpan = output.WrittenSpan;
+            Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+            output.Advance(0);
+            Assert.Equal(2, output.WrittenCount);
+            Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(previousSpan.SequenceEqual(output.WrittenSpan));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+        }
+
+        [Fact]
+        public static void InvalidAdvance()
+        {
+            {
+                var output = new ArrayBufferWriter<byte>();
+                Assert.Throws<ArgumentException>(() => output.Advance(-1));
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.Capacity + 1));
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>();
+                WriteData(output, 100);
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.FreeCapacity + 1));
+            }
+        }
+
+        public static bool IsX64 { get; } = IntPtr.Size >= 8;
+
+        [ConditionalFact(nameof(IsX64))]
+        [OuterLoop]
+        public static void InvalidAdvance_Large()
+        {
+            try
+            {
+                {
+                    var output = new ArrayBufferWriter<byte>(2_000_000_000);
+                    WriteData(output, 1_000);
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(int.MaxValue));
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(2_000_000_000 - 1_000 + 1));
+                }
+            }
+            catch (OutOfMemoryException) { }
+        }
+
+        [Fact]
+        public static void GetMemoryAndSpan()
+        {
+            {
+                var output = new ArrayBufferWriter<byte>();
+                WriteData(output, 2);
+                Span<byte> span = output.GetSpan();
+                Memory<byte> memory = output.GetMemory();
+                Span<byte> memorySpan = memory.Span;
+                Assert.True(span.Length > 0);
+                Assert.True(memorySpan.Length > 0);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>();
+                WriteData(output, 2);
+                ReadOnlyMemory<byte> writtenSoFarMemory = output.WrittenMemory;
+                ReadOnlySpan<byte> writtenSoFar = output.WrittenSpan;
+                Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar));
+                int previousAvailable = output.FreeCapacity;
+                Span<byte> span = output.GetSpan(500);
+                Assert.True(span.Length >= 500);
+                Assert.True(output.FreeCapacity >= 500);
+                Assert.True(output.FreeCapacity > previousAvailable);
+
+                Assert.Equal(writtenSoFar.Length, output.WrittenCount);
+                Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount)));
+
+                Memory<byte> memory = output.GetMemory();
+                Span<byte> memorySpan = memory.Span;
+                Assert.True(span.Length >= 500);
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+
+                memory = output.GetMemory(500);
+                memorySpan = memory.Span;
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < memorySpan.Length; i++)
+                {
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+        }
+
+        [Fact]
+        public static void GetSpanShouldAtleastDoubleWhenGrowing()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            WriteData(output, 100);
+            int previousAvailable = output.FreeCapacity;
+
+            _ = output.GetSpan(previousAvailable);
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+
+            _ = output.GetSpan(previousAvailable + 1);
+            Assert.True(output.FreeCapacity >= previousAvailable * 2);
+        }
+
+        [Fact]
+        public static void GetSpanOnlyGrowsAboveThreshold()
+        {
+            {
+                var output = new ArrayBufferWriter<byte>();
+                int previousAvailable = output.FreeCapacity;
+
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<byte>();
+                int previousAvailable = output.FreeCapacity;
+
+                _ = output.GetSpan(previousAvailable);
+                Assert.Equal(previousAvailable, output.FreeCapacity);
+
+                previousAvailable = output.FreeCapacity;
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+        }
+
+        [Fact]
+        public static void InvalidGetMemoryAndSpan()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            WriteData(output, 2);
+            Assert.Throws<ArgumentException>(() => output.GetSpan(-1));
+            Assert.Throws<ArgumentException>(() => output.GetMemory(-1));
+        }
+
+        [Fact]
+        public static void MultipleCallsToGetSpan()
+        {
+            var output = new ArrayBufferWriter<byte>(300);
+            int previousAvailable = output.FreeCapacity;
+            Assert.True(previousAvailable >= 300);
+            Assert.True(output.Capacity >= 300);
+            Assert.Equal(previousAvailable, output.Capacity);
+            Span<byte> span = output.GetSpan();
+            Assert.True(span.Length >= previousAvailable);
+            Assert.True(span.Length >= 256);
+            Span<byte> newSpan = output.GetSpan();
+            Assert.Equal(span.Length, newSpan.Length);
+
+            unsafe
+            {
+                void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+                void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan));
+                Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan);
+            }
+
+            Assert.Equal(span.Length, output.GetSpan().Length);
+        }
+
+        [Fact]
+        public static void WriteAndCopyToStream()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            WriteData(output, 100);
+            using (var memStream = new MemoryStream(100))
+            {
+                Assert.Equal(100, output.WrittenCount);
+
+                ReadOnlySpan<byte> outputSpan = output.WrittenMemory.ToArray();
+
+                ReadOnlyMemory<byte> transientMemory = output.WrittenMemory;
+                ReadOnlySpan<byte> transientSpan = output.WrittenSpan;
+
+                Assert.True(transientSpan.SequenceEqual(transientMemory.Span));
+
+                Assert.True(transientSpan[0] != 0);
+
+                memStream.Write(transientSpan.ToArray(), 0, transientSpan.Length);
+                output.Clear();
+
+                Assert.True(transientSpan[0] == 0);
+                Assert.True(transientMemory.Span[0] == 0);
+
+                Assert.Equal(0, output.WrittenCount);
+                byte[] streamOutput = memStream.ToArray();
+
+                Assert.True(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+                Assert.True(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenMemory.Span));
+                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+
+                Assert.Equal(outputSpan.Length, streamOutput.Length);
+                Assert.True(outputSpan.SequenceEqual(streamOutput));
+            }
+        }
+
+        [Fact]
+        public static async Task WriteAndCopyToStreamAsync()
+        {
+            var output = new ArrayBufferWriter<byte>();
+            WriteData(output, 100);
+            using (var memStream = new MemoryStream(100))
+            {
+                Assert.Equal(100, output.WrittenCount);
+
+                ReadOnlyMemory<byte> outputMemory = output.WrittenMemory.ToArray();
+
+                ReadOnlyMemory<byte> transient = output.WrittenMemory;
+
+                Assert.True(transient.Span[0] != 0);
+
+                await memStream.WriteAsync(transient.ToArray(), 0, transient.Length);
+                output.Clear();
+
+                Assert.True(transient.Span[0] == 0);
+
+                Assert.Equal(0, output.WrittenCount);
+                byte[] streamOutput = memStream.ToArray();
+
+                Assert.True(ReadOnlyMemory<byte>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+                Assert.True(ReadOnlySpan<byte>.Empty.SequenceEqual(output.WrittenMemory.Span));
+
+                Assert.Equal(outputMemory.Length, streamOutput.Length);
+                Assert.True(outputMemory.Span.SequenceEqual(streamOutput));
+            }
+        }
+
+        private static void WriteData(IBufferWriter<byte> bufferWriter, int numBytes)
+        {
+            Span<byte> outputSpan = bufferWriter.GetSpan(numBytes);
+            Debug.Assert(outputSpan.Length >= numBytes);
+            var random = new Random(42);
+
+            var data = new byte[numBytes];
+            random.NextBytes(data);
+            data.CopyTo(outputSpan);
+
+            bufferWriter.Advance(numBytes);
+        }
+    }
+}
diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs
new file mode 100644 (file)
index 0000000..8b5cf11
--- /dev/null
@@ -0,0 +1,303 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace System.Buffers.Tests
+{
+    public static partial class ArrayBufferWriterTests_Char
+    {
+        [Fact]
+        public static void ArrayBufferWriter_Ctor()
+        {
+            {
+                var output = new ArrayBufferWriter<char>();
+                Assert.True(output.FreeCapacity > 0);
+                Assert.True(output.Capacity > 0);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<char>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<char>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>(200);
+                Assert.True(output.FreeCapacity >= 200);
+                Assert.True(output.Capacity >= 200);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<char>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<char>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                ArrayBufferWriter<char> output = default;
+                Assert.Equal(null, output);
+            }
+        }
+
+        [Fact]
+        public static void Invalid_Ctor()
+        {
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<char>(0));
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<char>(-1));
+            Assert.Throws<OutOfMemoryException>(() => new ArrayBufferWriter<char>(int.MaxValue));
+        }
+
+        [Fact]
+        public static void Clear()
+        {
+            var output = new ArrayBufferWriter<char>();
+            int previousAvailable = output.FreeCapacity;
+            WriteData(output, 2);
+            Assert.True(output.FreeCapacity < previousAvailable);
+            Assert.True(output.WrittenCount > 0);
+            Assert.False(ReadOnlySpan<char>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.False(ReadOnlyMemory<char>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            output.Clear();
+            Assert.Equal(0, output.WrittenCount);
+            Assert.True(ReadOnlySpan<char>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.True(ReadOnlyMemory<char>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+        }
+
+        [Fact]
+        public static void Advance()
+        {
+            {
+                var output = new ArrayBufferWriter<char>();
+                int capacity = output.Capacity;
+                Assert.Equal(capacity, output.FreeCapacity);
+                output.Advance(output.FreeCapacity);
+                Assert.Equal(capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>();
+                output.Advance(output.Capacity);
+                Assert.Equal(output.Capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+                int previousCapacity = output.Capacity;
+                _ = output.GetSpan();
+                Assert.True(output.Capacity > previousCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>();
+                WriteData(output, 2);
+                ReadOnlyMemory<char> previousMemory = output.WrittenMemory;
+                ReadOnlySpan<char> previousSpan = output.WrittenSpan;
+                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+                output.Advance(10);
+                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
+                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            }
+        }
+
+        [Fact]
+        public static void AdvanceZero()
+        {
+            var output = new ArrayBufferWriter<char>();
+            WriteData(output, 2);
+            Assert.Equal(2, output.WrittenCount);
+            ReadOnlyMemory<char> previousMemory = output.WrittenMemory;
+            ReadOnlySpan<char> previousSpan = output.WrittenSpan;
+            Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+            output.Advance(0);
+            Assert.Equal(2, output.WrittenCount);
+            Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(previousSpan.SequenceEqual(output.WrittenSpan));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+        }
+
+        [Fact]
+        public static void InvalidAdvance()
+        {
+            {
+                var output = new ArrayBufferWriter<char>();
+                Assert.Throws<ArgumentException>(() => output.Advance(-1));
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.Capacity + 1));
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>();
+                WriteData(output, 100);
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.FreeCapacity + 1));
+            }
+        }
+
+        public static bool IsX64 { get; } = IntPtr.Size >= 8;
+
+        [ConditionalFact(nameof(IsX64))]
+        [OuterLoop]
+        public static void InvalidAdvance_Large()
+        {
+            try
+            {
+                {
+                    var output = new ArrayBufferWriter<char>(2_000_000_000);
+                    WriteData(output, 1_000);
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(int.MaxValue));
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(2_000_000_000 - 1_000 + 1));
+                }
+            }
+            catch (OutOfMemoryException) { }
+        }
+
+        [Fact]
+        public static void GetMemoryAndSpan()
+        {
+            {
+                var output = new ArrayBufferWriter<char>();
+                WriteData(output, 2);
+                Span<char> span = output.GetSpan();
+                Memory<char> memory = output.GetMemory();
+                Span<char> memorySpan = memory.Span;
+                Assert.True(span.Length > 0);
+                Assert.True(memorySpan.Length > 0);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>();
+                WriteData(output, 2);
+                ReadOnlyMemory<char> writtenSoFarMemory = output.WrittenMemory;
+                ReadOnlySpan<char> writtenSoFar = output.WrittenSpan;
+                Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar));
+                int previousAvailable = output.FreeCapacity;
+                Span<char> span = output.GetSpan(500);
+                Assert.True(span.Length >= 500);
+                Assert.True(output.FreeCapacity >= 500);
+                Assert.True(output.FreeCapacity > previousAvailable);
+
+                Assert.Equal(writtenSoFar.Length, output.WrittenCount);
+                Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount)));
+
+                Memory<char> memory = output.GetMemory();
+                Span<char> memorySpan = memory.Span;
+                Assert.True(span.Length >= 500);
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+
+                memory = output.GetMemory(500);
+                memorySpan = memory.Span;
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < memorySpan.Length; i++)
+                {
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+        }
+
+        [Fact]
+        public static void GetSpanShouldAtleastDoubleWhenGrowing()
+        {
+            var output = new ArrayBufferWriter<char>();
+            WriteData(output, 100);
+            int previousAvailable = output.FreeCapacity;
+
+            _ = output.GetSpan(previousAvailable);
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+
+            _ = output.GetSpan(previousAvailable + 1);
+            Assert.True(output.FreeCapacity >= previousAvailable * 2);
+        }
+
+        [Fact]
+        public static void GetSpanOnlyGrowsAboveThreshold()
+        {
+            {
+                var output = new ArrayBufferWriter<char>();
+                int previousAvailable = output.FreeCapacity;
+
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<char>();
+                int previousAvailable = output.FreeCapacity;
+
+                _ = output.GetSpan(previousAvailable);
+                Assert.Equal(previousAvailable, output.FreeCapacity);
+
+                previousAvailable = output.FreeCapacity;
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+        }
+
+        [Fact]
+        public static void InvalidGetMemoryAndSpan()
+        {
+            var output = new ArrayBufferWriter<char>();
+            WriteData(output, 2);
+            Assert.Throws<ArgumentException>(() => output.GetSpan(-1));
+            Assert.Throws<ArgumentException>(() => output.GetMemory(-1));
+        }
+
+        [Fact]
+        public static void MultipleCallsToGetSpan()
+        {
+            var output = new ArrayBufferWriter<char>(300);
+            int previousAvailable = output.FreeCapacity;
+            Assert.True(previousAvailable >= 300);
+            Assert.True(output.Capacity >= 300);
+            Assert.Equal(previousAvailable, output.Capacity);
+            Span<char> span = output.GetSpan();
+            Assert.True(span.Length >= previousAvailable);
+            Assert.True(span.Length >= 256);
+            Span<char> newSpan = output.GetSpan();
+            Assert.Equal(span.Length, newSpan.Length);
+
+            unsafe
+            {
+                void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+                void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan));
+                Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan);
+            }
+
+            Assert.Equal(span.Length, output.GetSpan().Length);
+        }
+
+        private static void WriteData(IBufferWriter<char> bufferWriter, int numChars)
+        {
+            Span<char> outputSpan = bufferWriter.GetSpan(numChars);
+            Debug.Assert(outputSpan.Length >= numChars);
+            var random = new Random(42);
+
+            var data = new char[numChars];
+
+            for (int i = 0; i < numChars; i++)
+            {
+                data[i] = (char)random.Next(0, char.MaxValue);
+            }
+
+            data.CopyTo(outputSpan);
+
+            bufferWriter.Advance(numChars);
+        }
+    }
+}
diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs
new file mode 100644 (file)
index 0000000..cfb16ab
--- /dev/null
@@ -0,0 +1,315 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+
+namespace System.Buffers.Tests
+{
+    public static partial class ArrayBufferWriterTests_String
+    {
+        [Fact]
+        public static void ArrayBufferWriter_Ctor()
+        {
+            {
+                var output = new ArrayBufferWriter<string>();
+                Assert.True(output.FreeCapacity > 0);
+                Assert.True(output.Capacity > 0);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<string>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<string>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>(200);
+                Assert.True(output.FreeCapacity >= 200);
+                Assert.True(output.Capacity >= 200);
+                Assert.Equal(0, output.WrittenCount);
+                Assert.True(ReadOnlySpan<string>.Empty.SequenceEqual(output.WrittenSpan));
+                Assert.True(ReadOnlyMemory<string>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            }
+
+            {
+                ArrayBufferWriter<string> output = default;
+                Assert.Equal(null, output);
+            }
+        }
+
+        [Fact]
+        public static void Invalid_Ctor()
+        {
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<string>(0));
+            Assert.Throws<ArgumentException>(() => new ArrayBufferWriter<string>(-1));
+            Assert.Throws<OutOfMemoryException>(() => new ArrayBufferWriter<string>(int.MaxValue));
+        }
+
+        [Fact]
+        public static void Clear()
+        {
+            var output = new ArrayBufferWriter<string>();
+            int previousAvailable = output.FreeCapacity;
+            WriteData(output, 2);
+            Assert.True(output.FreeCapacity < previousAvailable);
+            Assert.True(output.WrittenCount > 0);
+            Assert.False(ReadOnlySpan<string>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.False(ReadOnlyMemory<string>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            output.Clear();
+            Assert.Equal(0, output.WrittenCount);
+            Assert.True(ReadOnlySpan<string>.Empty.SequenceEqual(output.WrittenSpan));
+            Assert.True(ReadOnlyMemory<string>.Empty.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+        }
+
+        [Fact]
+        public static void Advance()
+        {
+            {
+                var output = new ArrayBufferWriter<string>();
+                int capacity = output.Capacity;
+                Assert.Equal(capacity, output.FreeCapacity);
+                output.Advance(output.FreeCapacity);
+                Assert.Equal(capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>();
+                output.Advance(output.Capacity);
+                Assert.Equal(output.Capacity, output.WrittenCount);
+                Assert.Equal(0, output.FreeCapacity);
+                int previousCapacity = output.Capacity;
+                _ = output.GetSpan();
+                Assert.True(output.Capacity > previousCapacity);
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>();
+                WriteData(output, 2);
+                ReadOnlyMemory<string> previousMemory = output.WrittenMemory;
+                ReadOnlySpan<string> previousSpan = output.WrittenSpan;
+                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+                output.Advance(10);
+                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
+                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+            }
+        }
+
+        [Fact]
+        public static void AdvanceZero()
+        {
+            var output = new ArrayBufferWriter<string>();
+            WriteData(output, 2);
+            Assert.Equal(2, output.WrittenCount);
+            ReadOnlyMemory<string> previousMemory = output.WrittenMemory;
+            ReadOnlySpan<string> previousSpan = output.WrittenSpan;
+            Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
+            output.Advance(0);
+            Assert.Equal(2, output.WrittenCount);
+            Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
+            Assert.True(previousSpan.SequenceEqual(output.WrittenSpan));
+            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
+        }
+
+        [Fact]
+        public static void InvalidAdvance()
+        {
+            {
+                var output = new ArrayBufferWriter<string>();
+                Assert.Throws<ArgumentException>(() => output.Advance(-1));
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.Capacity + 1));
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>();
+                WriteData(output, 100);
+                Assert.Throws<InvalidOperationException>(() => output.Advance(output.FreeCapacity + 1));
+            }
+        }
+
+        public static bool IsX64 { get; } = IntPtr.Size >= 8;
+
+        [ConditionalFact(nameof(IsX64))]
+        [OuterLoop]
+        public static void InvalidAdvance_Large()
+        {
+            try
+            {
+                {
+                    var output = new ArrayBufferWriter<string>(2_000_000_000);
+                    WriteData(output, 1_000);
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(int.MaxValue));
+                    Assert.Throws<InvalidOperationException>(() => output.Advance(2_000_000_000 - 1_000 + 1));
+                }
+            }
+            catch (OutOfMemoryException) { }
+        }
+
+        [Fact]
+        public static void GetMemoryAndSpan()
+        {
+            {
+                var output = new ArrayBufferWriter<string>();
+                WriteData(output, 2);
+                Span<string> span = output.GetSpan();
+                Memory<string> memory = output.GetMemory();
+                Span<string> memorySpan = memory.Span;
+                Assert.True(span.Length > 0);
+                Assert.True(memorySpan.Length > 0);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>();
+                WriteData(output, 2);
+                ReadOnlyMemory<string> writtenSoFarMemory = output.WrittenMemory;
+                ReadOnlySpan<string> writtenSoFar = output.WrittenSpan;
+                Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar));
+                int previousAvailable = output.FreeCapacity;
+                Span<string> span = output.GetSpan(500);
+                Assert.True(span.Length >= 500);
+                Assert.True(output.FreeCapacity >= 500);
+                Assert.True(output.FreeCapacity > previousAvailable);
+
+                Assert.Equal(writtenSoFar.Length, output.WrittenCount);
+                Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount)));
+
+                Memory<string> memory = output.GetMemory();
+                Span<string> memorySpan = memory.Span;
+                Assert.True(span.Length >= 500);
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < span.Length; i++)
+                {
+                    Assert.Equal(default, span[i]);
+                    Assert.Equal(default, memorySpan[i]);
+                }
+
+                memory = output.GetMemory(500);
+                memorySpan = memory.Span;
+                Assert.True(memorySpan.Length >= 500);
+                Assert.Equal(span.Length, memorySpan.Length);
+                for (int i = 0; i < memorySpan.Length; i++)
+                {
+                    Assert.Equal(default, memorySpan[i]);
+                }
+            }
+        }
+
+        [Fact]
+        public static void GetSpanShouldAtleastDoubleWhenGrowing()
+        {
+            var output = new ArrayBufferWriter<string>();
+            WriteData(output, 100);
+            int previousAvailable = output.FreeCapacity;
+
+            _ = output.GetSpan(previousAvailable);
+            Assert.Equal(previousAvailable, output.FreeCapacity);
+
+            _ = output.GetSpan(previousAvailable + 1);
+            Assert.True(output.FreeCapacity >= previousAvailable * 2);
+        }
+
+        [Fact]
+        public static void GetSpanOnlyGrowsAboveThreshold()
+        {
+            {
+                var output = new ArrayBufferWriter<string>();
+                int previousAvailable = output.FreeCapacity;
+
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+
+            {
+                var output = new ArrayBufferWriter<string>();
+                int previousAvailable = output.FreeCapacity;
+
+                _ = output.GetSpan(previousAvailable);
+                Assert.Equal(previousAvailable, output.FreeCapacity);
+
+                previousAvailable = output.FreeCapacity;
+                for (int i = 0; i < 10; i++)
+                {
+                    _ = output.GetSpan();
+                    Assert.Equal(previousAvailable, output.FreeCapacity);
+                }
+            }
+        }
+
+        [Fact]
+        public static void InvalidGetMemoryAndSpan()
+        {
+            var output = new ArrayBufferWriter<string>();
+            WriteData(output, 2);
+            Assert.Throws<ArgumentException>(() => output.GetSpan(-1));
+            Assert.Throws<ArgumentException>(() => output.GetMemory(-1));
+        }
+
+        [Fact]
+        public static void MultipleCallsToGetSpan()
+        {
+            var output = new ArrayBufferWriter<string>(300);
+            int previousAvailable = output.FreeCapacity;
+            Assert.True(previousAvailable >= 300);
+            Assert.True(output.Capacity >= 300);
+            Assert.Equal(previousAvailable, output.Capacity);
+            Span<string> span = output.GetSpan();
+            Assert.True(span.Length >= previousAvailable);
+            Assert.True(span.Length >= 256);
+            Span<string> newSpan = output.GetSpan();
+            Assert.Equal(span.Length, newSpan.Length);
+
+            unsafe
+            {
+                void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+                void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan));
+                Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan);
+            }
+
+            Assert.Equal(span.Length, output.GetSpan().Length);
+        }
+
+        private static void WriteData(IBufferWriter<string> bufferWriter, int numStrings)
+        {
+            Span<string> outputSpan = bufferWriter.GetSpan(numStrings);
+            Debug.Assert(outputSpan.Length >= numStrings);
+            var random = new Random(42);
+
+            var data = new string[numStrings];
+
+            for (int i = 0; i < numStrings; i++)
+            {
+                int length = random.Next(5, 10);
+                data[i] = GetRandomString(random, length, 32, 127);
+            }
+
+            data.CopyTo(outputSpan);
+
+            bufferWriter.Advance(numStrings);
+        }
+
+        private static string GetRandomString(Random r, int length, int minCodePoint, int maxCodePoint)
+        {
+            StringBuilder sb = new StringBuilder(length);
+            while (length-- != 0)
+            {
+                sb.Append((char)r.Next(minCodePoint, maxCodePoint));
+            }
+            return sb.ToString();
+        }
+    }
+}
index 92f23d7..ac76298 100644 (file)
@@ -9,6 +9,9 @@
   </PropertyGroup>
   <ItemGroup Condition="'$(IncludePartialFacadeTests)' == 'true'">
     <!-- Tests specific to netcoreapp and uap -->
+    <Compile Include="ArrayBufferWriter\ArrayBufferWriterTests.Byte.cs" />
+    <Compile Include="ArrayBufferWriter\ArrayBufferWriterTests.Char.cs" />
+    <Compile Include="ArrayBufferWriter\ArrayBufferWriterTests.String.cs" />
     <Compile Include="MemoryMarshal\AsRef.cs" />
     <Compile Include="MemoryMarshal\AsReadOnlyRef.cs" />
     <Compile Include="MemoryMarshal\CreateSpan.cs" />
index e2ba61f..40d70c6 100644 (file)
@@ -14,7 +14,7 @@
        the Common SR.cs into SourcePackageFiles. -->
   <Target Name="GetSourcesToPackage" BeforeTargets="ExpandProjectReferences">
     <ItemGroup>
-      <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" />
+      <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" AdditionalProperties="TargetGroup=netstandard"/>
     </ItemGroup>
 
     <MSBuild Projects="@(_ProjectsToBuild)"
diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.Manual.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.Manual.cs
new file mode 100644 (file)
index 0000000..23b8c70
--- /dev/null
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System.Text.Json
+{
+    public sealed partial class Utf8JsonWriter : System.IAsyncDisposable
+    {
+        public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
+    }
+}
index c70d8fa..d3c17c1 100644 (file)
@@ -71,9 +71,9 @@ namespace System.Text.Json
         public bool TryGetUInt32(out uint value) { throw null; }
         [System.CLSCompliantAttribute(false)]
         public bool TryGetUInt64(out ulong value) { throw null; }
-        public void WriteAsProperty(System.ReadOnlySpan<byte> utf8PropertyName, ref System.Text.Json.Utf8JsonWriter writer) { }
-        public void WriteAsProperty(System.ReadOnlySpan<char> propertyName, ref System.Text.Json.Utf8JsonWriter writer) { }
-        public void WriteAsValue(ref System.Text.Json.Utf8JsonWriter writer) { }
+        public void WriteAsProperty(System.ReadOnlySpan<byte> utf8PropertyName, System.Text.Json.Utf8JsonWriter writer) { }
+        public void WriteAsProperty(System.ReadOnlySpan<char> propertyName, System.Text.Json.Utf8JsonWriter writer) { }
+        public void WriteAsValue(System.Text.Json.Utf8JsonWriter writer) { }
         public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable<System.Text.Json.JsonElement>, System.Collections.Generic.IEnumerator<System.Text.Json.JsonElement>, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable
         {
             private object _dummy;
@@ -163,15 +163,6 @@ namespace System.Text.Json
         public bool Indented { get { throw null; } set { } }
         public bool SkipValidation { get { throw null; } set { } }
     }
-    public partial struct JsonWriterState
-    {
-        private object _dummy;
-        private int _dummyPrimitive;
-        public JsonWriterState(System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { throw null; }
-        public long BytesCommitted { get { throw null; } }
-        public long BytesWritten { get { throw null; } }
-        public System.Text.Json.JsonWriterOptions Options { get { throw null; } }
-    }
     public ref partial struct Utf8JsonReader
     {
         private object _dummy;
@@ -216,56 +207,60 @@ namespace System.Text.Json
         [System.CLSCompliantAttribute(false)]
         public bool TryGetUInt64(out ulong value) { throw null; }
     }
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter : System.IDisposable
     {
-        private object _dummy;
-        private int _dummyPrimitive;
-        public Utf8JsonWriter(System.Buffers.IBufferWriter<byte> bufferWriter, System.Text.Json.JsonWriterState state = default(System.Text.Json.JsonWriterState)) { throw null; }
+        public Utf8JsonWriter(System.Buffers.IBufferWriter<byte> bufferWriter, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { }
+        public Utf8JsonWriter(System.IO.Stream utf8Json, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { }
         public long BytesCommitted { get { throw null; } }
-        public long BytesWritten { get { throw null; } }
+        public int BytesPending { get { throw null; } }
         public int CurrentDepth { get { throw null; } }
-        public void Flush(bool isFinalBlock = true) { }
-        public System.Text.Json.JsonWriterState GetCurrentState() { throw null; }
-        public void WriteBoolean(System.ReadOnlySpan<byte> utf8PropertyName, bool value, bool escape = true) { }
-        public void WriteBoolean(System.ReadOnlySpan<char> propertyName, bool value, bool escape = true) { }
-        public void WriteBoolean(string propertyName, bool value, bool escape = true) { }
+        public System.Text.Json.JsonWriterOptions Options { get { throw null; } }
+        public void Dispose() { }
+        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<byte> bufferWriter) { }
+        public void Reset(System.IO.Stream utf8Json) { }
+        public void WriteBoolean(System.ReadOnlySpan<byte> utf8PropertyName, bool value) { }
+        public void WriteBoolean(System.ReadOnlySpan<char> propertyName, bool value) { }
+        public void WriteBoolean(string propertyName, bool value) { }
         public void WriteBooleanValue(bool value) { }
-        public void WriteCommentValue(System.ReadOnlySpan<byte> utf8Value, bool escape = true) { }
-        public void WriteCommentValue(System.ReadOnlySpan<char> value, bool escape = true) { }
-        public void WriteCommentValue(string value, bool escape = true) { }
+        public void WriteCommentValue(System.ReadOnlySpan<byte> utf8Value) { }
+        public void WriteCommentValue(System.ReadOnlySpan<char> value) { }
+        public void WriteCommentValue(string value) { }
         public void WriteEndArray() { }
         public void WriteEndObject() { }
-        public void WriteNull(System.ReadOnlySpan<byte> utf8PropertyName, bool escape = true) { }
-        public void WriteNull(System.ReadOnlySpan<char> propertyName, bool escape = true) { }
-        public void WriteNull(string propertyName, bool escape = true) { }
+        public void WriteNull(System.ReadOnlySpan<byte> utf8PropertyName) { }
+        public void WriteNull(System.ReadOnlySpan<char> propertyName) { }
+        public void WriteNull(string propertyName) { }
         public void WriteNullValue() { }
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, decimal value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, double value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, int value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, long value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, float value, bool escape = true) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, decimal value) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, double value) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, int value) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, long value) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, float value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, uint value, bool escape = true) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, uint value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, ulong value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, decimal value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, double value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, int value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, long value, bool escape = true) { }
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, float value, bool escape = true) { }
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, ulong value) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, decimal value) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, double value) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, int value) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, long value) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, float value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, uint value, bool escape = true) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, uint value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(System.ReadOnlySpan<char> propertyName, ulong value, bool escape = true) { }
-        public void WriteNumber(string propertyName, decimal value, bool escape = true) { }
-        public void WriteNumber(string propertyName, double value, bool escape = true) { }
-        public void WriteNumber(string propertyName, int value, bool escape = true) { }
-        public void WriteNumber(string propertyName, long value, bool escape = true) { }
-        public void WriteNumber(string propertyName, float value, bool escape = true) { }
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, ulong value) { }
+        public void WriteNumber(string propertyName, decimal value) { }
+        public void WriteNumber(string propertyName, double value) { }
+        public void WriteNumber(string propertyName, int value) { }
+        public void WriteNumber(string propertyName, long value) { }
+        public void WriteNumber(string propertyName, float value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(string propertyName, uint value, bool escape = true) { }
+        public void WriteNumber(string propertyName, uint value) { }
         [System.CLSCompliantAttribute(false)]
-        public void WriteNumber(string propertyName, ulong value, bool escape = true) { }
+        public void WriteNumber(string propertyName, ulong value) { }
         public void WriteNumberValue(decimal value) { }
         public void WriteNumberValue(double value) { }
         public void WriteNumberValue(int value) { }
@@ -276,37 +271,37 @@ namespace System.Text.Json
         [System.CLSCompliantAttribute(false)]
         public void WriteNumberValue(ulong value) { }
         public void WriteStartArray() { }
-        public void WriteStartArray(System.ReadOnlySpan<byte> utf8PropertyName, bool escape = true) { }
-        public void WriteStartArray(System.ReadOnlySpan<char> propertyName, bool escape = true) { }
-        public void WriteStartArray(string propertyName, bool escape = true) { }
+        public void WriteStartArray(System.ReadOnlySpan<byte> utf8PropertyName) { }
+        public void WriteStartArray(System.ReadOnlySpan<char> propertyName) { }
+        public void WriteStartArray(string propertyName) { }
         public void WriteStartObject() { }
-        public void WriteStartObject(System.ReadOnlySpan<byte> utf8PropertyName, bool escape = true) { }
-        public void WriteStartObject(System.ReadOnlySpan<char> propertyName, bool escape = true) { }
-        public void WriteStartObject(string propertyName, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.DateTime value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.DateTimeOffset value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.Guid value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.ReadOnlySpan<byte> utf8Value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.ReadOnlySpan<char> value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, string value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, System.DateTime value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, System.DateTimeOffset value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, System.Guid value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, System.ReadOnlySpan<byte> utf8Value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, System.ReadOnlySpan<char> value, bool escape = true) { }
-        public void WriteString(System.ReadOnlySpan<char> propertyName, string value, bool escape = true) { }
-        public void WriteString(string propertyName, System.DateTime value, bool escape = true) { }
-        public void WriteString(string propertyName, System.DateTimeOffset value, bool escape = true) { }
-        public void WriteString(string propertyName, System.Guid value, bool escape = true) { }
-        public void WriteString(string propertyName, System.ReadOnlySpan<byte> utf8Value, bool escape = true) { }
-        public void WriteString(string propertyName, System.ReadOnlySpan<char> value, bool escape = true) { }
-        public void WriteString(string propertyName, string value, bool escape = true) { }
+        public void WriteStartObject(System.ReadOnlySpan<byte> utf8PropertyName) { }
+        public void WriteStartObject(System.ReadOnlySpan<char> propertyName) { }
+        public void WriteStartObject(string propertyName) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.DateTime value) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.DateTimeOffset value) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.Guid value) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.ReadOnlySpan<byte> utf8Value) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, System.ReadOnlySpan<char> value) { }
+        public void WriteString(System.ReadOnlySpan<byte> utf8PropertyName, string value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, System.DateTime value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, System.DateTimeOffset value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, System.Guid value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, System.ReadOnlySpan<byte> utf8Value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, System.ReadOnlySpan<char> value) { }
+        public void WriteString(System.ReadOnlySpan<char> propertyName, string value) { }
+        public void WriteString(string propertyName, System.DateTime value) { }
+        public void WriteString(string propertyName, System.DateTimeOffset value) { }
+        public void WriteString(string propertyName, System.Guid value) { }
+        public void WriteString(string propertyName, System.ReadOnlySpan<byte> utf8Value) { }
+        public void WriteString(string propertyName, System.ReadOnlySpan<char> value) { }
+        public void WriteString(string propertyName, string value) { }
         public void WriteStringValue(System.DateTime value) { }
         public void WriteStringValue(System.DateTimeOffset value) { }
         public void WriteStringValue(System.Guid value) { }
-        public void WriteStringValue(System.ReadOnlySpan<byte> utf8Value, bool escape = true) { }
-        public void WriteStringValue(System.ReadOnlySpan<char> value, bool escape = true) { }
-        public void WriteStringValue(string value, bool escape = true) { }
+        public void WriteStringValue(System.ReadOnlySpan<byte> utf8Value) { }
+        public void WriteStringValue(System.ReadOnlySpan<char> value) { }
+        public void WriteStringValue(string value) { }
     }
 }
 namespace System.Text.Json.Serialization
index 002d0f8..0914d09 100644 (file)
@@ -7,6 +7,7 @@
     <Compile Include="System.Text.Json.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetGroup)' != 'netstandard'">
+    <Compile Include="System.Text.Json.Manual.cs" />
     <ProjectReference Include="..\..\System.Memory\ref\System.Memory.csproj" />
     <ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
   </ItemGroup>
index 0cd15e0..d2eff55 100644 (file)
   <data name="FailedToGetLargerSpan" xml:space="preserve">
     <value>The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing.</value>
   </data>
-  <data name="FailedToGetMinimumSizeSpan" xml:space="preserve">
-    <value>The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing. Need at least {0} bytes.</value>
-  </data>
   <data name="FoundInvalidCharacter" xml:space="preserve">
     <value>'{0}' is invalid after a value. Expected either ',', '}}', or ']'.</value>
   </data>
   <data name="SerializerOptionsImmutable" xml:space="preserve">
     <value>Serializer options cannot be changed once serialization or deserialization has occurred.</value>
   </data>
+  <data name="StreamNotWritable" xml:space="preserve">
+    <value>Stream was not writable.</value>
+  </data>
+  <data name="CannotWriteCommentWithEmbeddedDelimiter" xml:space="preserve">
+    <value>Cannot write a comment value which contains the end of comment delimiter.</value>
+  </data>
   <data name="SerializerPropertyNameConflict" xml:space="preserve">
     <value>The property '{0}.{1}' has the same name as a previous property based on naming or casing policies.</value>
   </data>
index b8d9ac1..c3c7e37 100644 (file)
@@ -9,6 +9,7 @@
     <!-- BUILDING_INBOX_LIBRARY is only false when building for netstandard to validate that the sources are netstandard compatible. -->
     <!-- This is meant to help with producing a source package and not to ship a netstandard compatible binary. -->
     <DefineConstants Condition="'$(TargetsNETStandard)' != 'true'">$(DefineConstants);BUILDING_INBOX_LIBRARY</DefineConstants>
+    <DefineConstants Condition="'$(TargetsNETStandard)' == 'true'">$(DefineConstants);USE_ABW_INTERNALLY</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="System\Text\Json\BitStack.cs" />
@@ -41,7 +42,6 @@
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.MultiSegment.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
-    <Compile Include="System\Text\Json\Serialization\ArrayBufferWriter.cs" />
     <Compile Include="System\Text\Json\Serialization\ClassMaterializer.cs" />
     <Compile Include="System\Text\Json\Serialization\ClassType.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\DefaultArrayConverter.cs" />
@@ -93,9 +93,9 @@
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleEnumerable.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleObject.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleValue.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
     <Compile Include="System\Text\Json\Serialization\Policies\JsonValueConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\PooledBufferWriter.cs" />
     <Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
     <Compile Include="System\Text\Json\Serialization\ReadStack.cs" />
     <Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterHelper.Escaping.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterHelper.Transcoding.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterOptions.cs" />
-    <Compile Include="System\Text\Json\Writer\JsonWriterState.cs" />
     <Compile Include="System\Text\Json\Writer\SequenceValidity.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.DateTime.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.String.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.UnsignedNumber.cs" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetsNETStandard)' == 'true'">
+    <!-- Common or Common-branched source files -->
+    <Compile Include="$(CommonPath)\System\Buffers\ArrayBufferWriter.cs">
+      <Link>Common\System\Buffers\ArrayBufferWriter.cs</Link>
+    </Compile>
+  </ItemGroup>
   <ItemGroup Condition="'$(TargetsNETStandard)' != 'true'">
     <Reference Include="System.Collections" />
     <Reference Include="System.Diagnostics.Debug" />
index a77f5a8..65766b8 100644 (file)
@@ -603,7 +603,7 @@ namespace System.Text.Json
         /// </summary>
         internal void WriteElementTo(
             int index,
-            ref Utf8JsonWriter writer,
+            Utf8JsonWriter writer,
             ReadOnlySpan<char> propertyName)
         {
             CheckNotDisposed();
@@ -614,14 +614,14 @@ namespace System.Text.Json
             {
                 case JsonTokenType.StartObject:
                     writer.WriteStartObject(propertyName);
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.StartArray:
                     writer.WriteStartArray(propertyName);
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.String:
-                    WriteString(propertyName, row, ref writer);
+                    WriteString(propertyName, row, writer);
                     return;
                 case JsonTokenType.True:
                     writer.WriteBoolean(propertyName, value: true);
@@ -647,7 +647,7 @@ namespace System.Text.Json
         /// </summary>
         internal void WriteElementTo(
             int index,
-            ref Utf8JsonWriter writer,
+            Utf8JsonWriter writer,
             ReadOnlySpan<byte> propertyName)
         {
             CheckNotDisposed();
@@ -658,14 +658,14 @@ namespace System.Text.Json
             {
                 case JsonTokenType.StartObject:
                     writer.WriteStartObject(propertyName);
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.StartArray:
                     writer.WriteStartArray(propertyName);
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.String:
-                    WriteString(propertyName, row, ref writer);
+                    WriteString(propertyName, row, writer);
                     return;
                 case JsonTokenType.True:
                     writer.WriteBoolean(propertyName, value: true);
@@ -691,7 +691,7 @@ namespace System.Text.Json
         /// </summary>
         internal void WriteElementTo(
             int index,
-            ref Utf8JsonWriter writer)
+            Utf8JsonWriter writer)
         {
             CheckNotDisposed();
 
@@ -701,14 +701,14 @@ namespace System.Text.Json
             {
                 case JsonTokenType.StartObject:
                     writer.WriteStartObject();
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.StartArray:
                     writer.WriteStartArray();
-                    WriteComplexElement(index, ref writer);
+                    WriteComplexElement(index, writer);
                     return;
                 case JsonTokenType.String:
-                    WriteString(row, ref writer);
+                    WriteString(row, writer);
                     return;
                 case JsonTokenType.Number:
                     writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span);
@@ -727,7 +727,7 @@ namespace System.Text.Json
             Debug.Fail($"Unexpected encounter with JsonTokenType {row.TokenType}");
         }
 
-        private void WriteComplexElement(int index, ref Utf8JsonWriter writer)
+        private void WriteComplexElement(int index, Utf8JsonWriter writer)
         {
             int endIndex = GetEndIndex(index, true);
 
@@ -739,7 +739,7 @@ namespace System.Text.Json
                 switch (row.TokenType)
                 {
                     case JsonTokenType.String:
-                        WriteString(row, ref writer);
+                        WriteString(row, writer);
                         continue;
                     case JsonTokenType.Number:
                         writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span);
@@ -778,7 +778,7 @@ namespace System.Text.Json
                         switch (propertyValue.TokenType)
                         {
                             case JsonTokenType.String:
-                                WriteString(propertyName, propertyValue, ref writer);
+                                WriteString(propertyName, propertyValue, writer);
                                 continue;
                             case JsonTokenType.Number:
                                 writer.WriteNumber(
@@ -844,7 +844,7 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteString(ReadOnlySpan<byte> propertyName, in DbRow row, ref Utf8JsonWriter writer)
+        private void WriteString(ReadOnlySpan<byte> propertyName, in DbRow row, Utf8JsonWriter writer)
         {
             ArraySegment<byte> rented = default;
 
@@ -860,7 +860,7 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteString(ReadOnlySpan<char> propertyName, in DbRow row, ref Utf8JsonWriter writer)
+        private void WriteString(ReadOnlySpan<char> propertyName, in DbRow row, Utf8JsonWriter writer)
         {
             ArraySegment<byte> rented = default;
 
@@ -877,7 +877,7 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteString(in DbRow row, ref Utf8JsonWriter writer)
+        private void WriteString(in DbRow row, Utf8JsonWriter writer)
         {
             ArraySegment<byte> rented = default;
 
index 03d2698..1ff367a 100644 (file)
@@ -940,11 +940,11 @@ namespace System.Text.Json
         /// <exception cref="ObjectDisposedException">
         ///   The parent <see cref="JsonDocument"/> has been disposed.
         /// </exception>
-        public void WriteAsProperty(ReadOnlySpan<char> propertyName, ref Utf8JsonWriter writer)
+        public void WriteAsProperty(ReadOnlySpan<char> propertyName, Utf8JsonWriter writer)
         {
             CheckValidInstance();
 
-            _parent.WriteElementTo(_idx, ref writer, propertyName);
+            _parent.WriteElementTo(_idx, writer, propertyName);
         }
 
         /// <summary>
@@ -960,11 +960,11 @@ namespace System.Text.Json
         /// <exception cref="ObjectDisposedException">
         ///   The parent <see cref="JsonDocument"/> has been disposed.
         /// </exception>
-        public void WriteAsProperty(ReadOnlySpan<byte> utf8PropertyName, ref Utf8JsonWriter writer)
+        public void WriteAsProperty(ReadOnlySpan<byte> utf8PropertyName, Utf8JsonWriter writer)
         {
             CheckValidInstance();
 
-            _parent.WriteElementTo(_idx, ref writer, utf8PropertyName);
+            _parent.WriteElementTo(_idx, writer, utf8PropertyName);
         }
 
         /// <summary>
@@ -977,11 +977,11 @@ namespace System.Text.Json
         /// <exception cref="ObjectDisposedException">
         ///   The parent <see cref="JsonDocument"/> has been disposed.
         /// </exception>
-        public void WriteAsValue(ref Utf8JsonWriter writer)
+        public void WriteAsValue(Utf8JsonWriter writer)
         {
             CheckValidInstance();
 
-            _parent.WriteElementTo(_idx, ref writer);
+            _parent.WriteElementTo(_idx, writer);
         }
 
         /// <summary>
index 918c1e4..9526f67 100644 (file)
@@ -20,12 +20,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(bool value, ref Utf8JsonWriter writer)
+        public override void Write(bool value, Utf8JsonWriter writer)
         {
             writer.WriteBooleanValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, bool value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, bool value, Utf8JsonWriter writer)
         {
             writer.WriteBoolean(escapedPropertyName, value);
         }
index ce2a95f..174a87b 100644 (file)
@@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(byte value, ref Utf8JsonWriter writer)
+        public override void Write(byte value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, byte value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, byte value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 30aa4e7..1072743 100644 (file)
@@ -21,20 +21,20 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(char value, ref Utf8JsonWriter writer)
+        public override void Write(char value, Utf8JsonWriter writer)
         {
 #if BUILDING_INBOX_LIBRARY
-            Span<char> temp = MemoryMarshal.CreateSpan<char>(ref value, 1);
+            Span<char> temp = MemoryMarshal.CreateSpan(ref value, 1);
             writer.WriteStringValue(temp);
 #else
             writer.WriteStringValue(value.ToString());
 #endif
         }
 
-        public override void Write(Span<byte> escapedPropertyName, char value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, char value, Utf8JsonWriter writer)
         {
 #if BUILDING_INBOX_LIBRARY
-            Span<char> temp = MemoryMarshal.CreateSpan<char>(ref value, 1);
+            Span<char> temp = MemoryMarshal.CreateSpan(ref value, 1);
             writer.WriteString(escapedPropertyName, temp);
 #else
             writer.WriteString(escapedPropertyName, value.ToString());
index 7b49f8e..f15470c 100644 (file)
@@ -13,12 +13,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetDateTime(out value);
         }
 
-        public override void Write(DateTime value, ref Utf8JsonWriter writer)
+        public override void Write(DateTime value, Utf8JsonWriter writer)
         {
             writer.WriteStringValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, DateTime value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, DateTime value, Utf8JsonWriter writer)
         {
             writer.WriteString(escapedPropertyName, value);
         }
index a19b082..0f9d6d3 100644 (file)
@@ -13,12 +13,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetDateTimeOffset(out value);
         }
 
-        public override void Write(DateTimeOffset value, ref Utf8JsonWriter writer)
+        public override void Write(DateTimeOffset value, Utf8JsonWriter writer)
         {
             writer.WriteStringValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, DateTimeOffset value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, DateTimeOffset value, Utf8JsonWriter writer)
         {
             writer.WriteString(escapedPropertyName, value);
         }
index b5dcb88..ed90587 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetDecimal(out value);
         }
 
-        public override void Write(decimal value, ref Utf8JsonWriter writer)
+        public override void Write(decimal value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, decimal value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, decimal value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 91c7896..5d23696 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetDouble(out value);
         }
 
-        public override void Write(double value, ref Utf8JsonWriter writer)
+        public override void Write(double value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, double value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, double value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 10f08c1..b99dcc9 100644 (file)
@@ -44,7 +44,7 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(TValue value, ref Utf8JsonWriter writer)
+        public override void Write(TValue value, Utf8JsonWriter writer)
         {
             if (TreatAsString)
             {
@@ -64,7 +64,7 @@ namespace System.Text.Json.Serialization.Converters
             }
         }
 
-        public override void Write(Span<byte> escapedPropertyName, TValue value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, TValue value, Utf8JsonWriter writer)
         {
             if (TreatAsString)
             {
index 0ddac2d..16fe1d5 100644 (file)
@@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(short value, ref Utf8JsonWriter writer)
+        public override void Write(short value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, short value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, short value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 420d6b4..de21818 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetInt32(out value);
         }
 
-        public override void Write(int value, ref Utf8JsonWriter writer)
+        public override void Write(int value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, int value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, int value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index d5ca306..b9d18dd 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetInt64(out value);
         }
 
-        public override void Write(long value, ref Utf8JsonWriter writer)
+        public override void Write(long value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, long value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, long value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 4bbb0ea..786a740 100644 (file)
@@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(sbyte value, ref Utf8JsonWriter writer)
+        public override void Write(sbyte value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, sbyte value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, sbyte value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 48f75b7..32093e3 100644 (file)
@@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(float value, ref Utf8JsonWriter writer)
+        public override void Write(float value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, float value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, float value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index e892602..f5120cf 100644 (file)
@@ -20,12 +20,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(string value, ref Utf8JsonWriter writer)
+        public override void Write(string value, Utf8JsonWriter writer)
         {
             writer.WriteStringValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, string value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, string value, Utf8JsonWriter writer)
         {
             writer.WriteString(escapedPropertyName, value);
         }
index a169731..cb232e6 100644 (file)
@@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        public override void Write(ushort value, ref Utf8JsonWriter writer)
+        public override void Write(ushort value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, ushort value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, ushort value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 1337c56..94e7dfc 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetUInt32(out value);
         }
 
-        public override void Write(uint value, ref Utf8JsonWriter writer)
+        public override void Write(uint value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, uint value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, uint value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 60ff3d1..7a52f4b 100644 (file)
@@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters
             return reader.TryGetUInt64(out value);
         }
 
-        public override void Write(ulong value, ref Utf8JsonWriter writer)
+        public override void Write(ulong value, Utf8JsonWriter writer)
         {
             writer.WriteNumberValue(value);
         }
 
-        public override void Write(Span<byte> escapedPropertyName, ulong value, ref Utf8JsonWriter writer)
+        public override void Write(Span<byte> escapedPropertyName, ulong value, Utf8JsonWriter writer)
         {
             writer.WriteNumber(escapedPropertyName, value);
         }
index 3624b6f..448f5eb 100644 (file)
@@ -255,7 +255,8 @@ namespace System.Text.Json.Serialization
         internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
         internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options);
 
-        internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer);
-        internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer);
+        internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
+
+        internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
     }
 }
index a234ed4..ce4a94f 100644 (file)
@@ -82,13 +82,13 @@ namespace System.Text.Json.Serialization
         }
 
         // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call.
-        internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (current.Enumerator != null)
             {
                 // Forward the setter to the value-based JsonPropertyInfo.
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.WriteEnumerable(options, ref current, ref writer);
+                propertyInfo.WriteEnumerable(options, ref current, writer);
             }
             else if (ShouldSerialize)
             {
@@ -117,17 +117,17 @@ namespace System.Text.Json.Serialization
                 {
                     if (_escapedName != null)
                     {
-                        ValueConverter.Write(_escapedName, value, ref writer);
+                        ValueConverter.Write(_escapedName, value, writer);
                     }
                     else
                     {
-                        ValueConverter.Write(value, ref writer);
+                        ValueConverter.Write(value, writer);
                     }
                 }
             }
         }
 
-        internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (ValueConverter != null)
             {
@@ -150,7 +150,7 @@ namespace System.Text.Json.Serialization
                 }
                 else
                 {
-                    ValueConverter.Write(value, ref writer);
+                    ValueConverter.Write(value, writer);
                 }
             }
         }
index c159722..44cc436 100644 (file)
@@ -80,13 +80,13 @@ namespace System.Text.Json.Serialization
         }
 
         // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call.
-        internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (current.Enumerator != null)
             {
                 // Forward the setter to the value-based JsonPropertyInfo.
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.WriteEnumerable(options, ref current, ref writer);
+                propertyInfo.WriteEnumerable(options, ref current, writer);
             }
             else if (ShouldSerialize)
             {
@@ -115,17 +115,17 @@ namespace System.Text.Json.Serialization
                 {
                     if (_escapedName != null)
                     {
-                        ValueConverter.Write(_escapedName, value.GetValueOrDefault(), ref writer);
+                        ValueConverter.Write(_escapedName, value.GetValueOrDefault(), writer);
                     }
                     else
                     {
-                        ValueConverter.Write(value.GetValueOrDefault(), ref writer);
+                        ValueConverter.Write(value.GetValueOrDefault(), writer);
                     }
                 }
             }
         }
 
-        internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (ValueConverter != null)
             {
@@ -148,7 +148,7 @@ namespace System.Text.Json.Serialization
                 }
                 else
                 {
-                    ValueConverter.Write(value.GetValueOrDefault(), ref writer);
+                    ValueConverter.Write(value.GetValueOrDefault(), writer);
                 }
             }
         }
index 419e379..da494ef 100644 (file)
@@ -9,18 +9,10 @@ namespace System.Text.Json.Serialization
 {
     public static partial class JsonSerializer
     {
-        private static bool WriteEnumerable(
-            JsonSerializerOptions options,
-            ref Utf8JsonWriter writer,
-            ref WriteStack state)
-        {
-            return HandleEnumerable(state.Current.JsonClassInfo.ElementClassInfo, options, ref writer, ref state);
-        }
-
         private static bool HandleEnumerable(
             JsonClassInfo elementClassInfo,
             JsonSerializerOptions options,
-            ref Utf8JsonWriter writer,
+            Utf8JsonWriter writer,
             ref WriteStack state)
         {
             Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);
@@ -66,7 +58,7 @@ namespace System.Text.Json.Serialization
 
                 if (elementClassInfo.ClassType == ClassType.Value)
                 {
-                    elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, ref writer);
+                    elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, writer);
                 }
                 else if (state.Current.Enumerator.Current == null)
                 {
index ee4b796..17c5c1c 100644 (file)
@@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization
     {
         private static bool WriteObject(
             JsonSerializerOptions options,
-            ref Utf8JsonWriter writer,
+            Utf8JsonWriter writer,
             ref WriteStack state)
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
@@ -32,7 +32,7 @@ namespace System.Text.Json.Serialization
             // Determine if we are done enumerating properties.
             if (state.Current.PropertyIndex != classInfo.PropertyCount)
             {
-                HandleObject(options, ref writer, ref state);
+                HandleObject(options, writer, ref state);
                 return false;
             }
 
@@ -52,7 +52,7 @@ namespace System.Text.Json.Serialization
 
         private static bool HandleObject(
                 JsonSerializerOptions options,
-                ref Utf8JsonWriter writer,
+                Utf8JsonWriter writer,
                 ref WriteStack state)
         {
             Debug.Assert(
@@ -76,7 +76,7 @@ namespace System.Text.Json.Serialization
 
             if (jsonPropertyInfo.ClassType == ClassType.Value)
             {
-                jsonPropertyInfo.Write(options, ref state.Current, ref writer);
+                jsonPropertyInfo.Write(options, ref state.Current, writer);
                 state.Current.NextProperty();
                 return true;
             }
@@ -84,7 +84,7 @@ namespace System.Text.Json.Serialization
             // A property that returns an enumerator keeps the same stack frame.
             if (jsonPropertyInfo.ClassType == ClassType.Enumerable)
             {
-                bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, ref writer, ref state);
+                bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, writer, ref state);
                 if (endOfEnumerable)
                 {
                     state.Current.NextProperty();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs
deleted file mode 100644 (file)
index 5024f26..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace System.Text.Json.Serialization
-{
-    public static partial class JsonSerializer
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool WriteValue(
-            JsonSerializerOptions options,
-            ref Utf8JsonWriter writer,
-            ref WriteStackFrame current)
-        {
-            Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value);
-
-            current.JsonPropertyInfo.Write(options, ref current, ref writer);
-            return true;
-        }
-    }
-}
index 66d45d4..ab80dd5 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Diagnostics;
 
 namespace System.Text.Json.Serialization
@@ -55,15 +54,6 @@ namespace System.Text.Json.Serialization
             }
         }
 
-        private static void WriteNull(
-            ref JsonWriterState writerState,
-            IBufferWriter<byte> bufferWriter)
-        {
-            Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState);
-            writer.WriteNullValue();
-            writer.Flush(true);
-        }
-
         private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options)
         {
             if (options == null)
@@ -73,7 +63,7 @@ namespace System.Text.Json.Serialization
 
             byte[] result;
 
-            using (var output = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
+            using (var output = new PooledBufferWriter<byte>(options.DefaultBufferSize))
             {
                 WriteCore(output, value, type, options);
                 result = output.WrittenMemory.ToArray();
@@ -91,7 +81,7 @@ namespace System.Text.Json.Serialization
 
             string result;
 
-            using (var output = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
+            using (var output = new PooledBufferWriter<byte>(options.DefaultBufferSize))
             {
                 WriteCore(output, value, type, options);
                 result = JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
@@ -100,12 +90,11 @@ namespace System.Text.Json.Serialization
             return result;
         }
 
-        private static void WriteCore(ArrayBufferWriter<byte> output, object value, Type type, JsonSerializerOptions options)
+        private static void WriteCore(PooledBufferWriter<byte> output, object value, Type type, JsonSerializerOptions options)
         {
             Debug.Assert(type != null || value == null);
 
-            var writerState = new JsonWriterState(options.GetWriterOptions());
-            var writer = new Utf8JsonWriter(output, writerState);
+            var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
 
             if (value == null)
             {
@@ -123,10 +112,10 @@ namespace System.Text.Json.Serialization
                 state.Current.Initialize(type, options);
                 state.Current.CurrentValue = value;
 
-                Write(ref writer, -1, options, ref state);
+                Write(writer, -1, options, ref state);
             }
 
-            writer.Flush(isFinalBlock: true);
+            writer.Flush();
         }
     }
 }
index 6987a7c..6a53e3a 100644 (file)
@@ -50,13 +50,16 @@ namespace System.Text.Json.Serialization
                 options = JsonSerializerOptions.s_defaultOptions;
             }
 
-            var writerState = new JsonWriterState(options.GetWriterOptions());
+            JsonWriterOptions writerOptions = options.GetWriterOptions();
 
-            using (var bufferWriter = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
+            using (var bufferWriter = new PooledBufferWriter<byte>(options.DefaultBufferSize))
+            using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
             {
                 if (value == null)
                 {
-                    WriteNull(ref writerState, bufferWriter);
+                    writer.WriteNullValue();
+                    writer.Flush();
+
 #if BUILDING_INBOX_LIBRARY
                     await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false);
 #else
@@ -82,7 +85,9 @@ namespace System.Text.Json.Serialization
                 {
                     flushThreshold = (int)(bufferWriter.Capacity * .9); //todo: determine best value here
 
-                    isFinalBlock = Write(ref writerState, bufferWriter, flushThreshold, options, ref state);
+                    isFinalBlock = Write(writer, flushThreshold, options, ref state);
+                    writer.Flush();
+
 #if BUILDING_INBOX_LIBRARY
                     await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false);
 #else
index 0b64163..1913e73 100644 (file)
@@ -9,33 +9,12 @@ namespace System.Text.Json.Serialization
 {
     public static partial class JsonSerializer
     {
-        private static bool Write(
-            ref JsonWriterState writerState,
-            IBufferWriter<byte> bufferWriter,
-            int flushThreshold,
-            JsonSerializerOptions options,
-            ref WriteStack state)
-        {
-            Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState);
-
-            bool isFinalBlock = Write(
-                ref writer,
-                flushThreshold,
-                options,
-                ref state);
-
-            writer.Flush(isFinalBlock: isFinalBlock);
-            writerState = writer.GetCurrentState();
-
-            return isFinalBlock;
-        }
-
         // There are three conditions to consider for an object (primitive value, enumerable or object) being processed here:
         // 1) The object type was specified as the root-level return type to a Parse\Read method.
         // 2) The object is property on a parent object.
         // 3) The object is an element in an enumerable.
         private static bool Write(
-            ref Utf8JsonWriter writer,
+            Utf8JsonWriter writer,
             int flushThreshold,
             JsonSerializerOptions options,
             ref WriteStack state)
@@ -44,26 +23,29 @@ namespace System.Text.Json.Serialization
             bool finishedSerializing;
             do
             {
-                switch (state.Current.JsonClassInfo.ClassType)
+                WriteStackFrame current = state.Current;
+                switch (current.JsonClassInfo.ClassType)
                 {
                     case ClassType.Enumerable:
-                        finishedSerializing = WriteEnumerable(options, ref writer, ref state);
+                        finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
                         break;
                     case ClassType.Value:
-                        finishedSerializing = WriteValue(options, ref writer, ref state.Current);
+                        Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value);
+                        current.JsonPropertyInfo.Write(options, ref current, writer);
+                        finishedSerializing = true;
                         break;
                     case ClassType.Object:
-                        finishedSerializing = WriteObject(options, ref writer, ref state);
+                        finishedSerializing = WriteObject(options, writer, ref state);
                         break;
                     default:
                         Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown);
 
                         // Treat typeof(object) as an empty object.
-                        finishedSerializing = WriteObject(options, ref writer, ref state);
+                        finishedSerializing = WriteObject(options, writer, ref state);
                         break;
                 }
 
-                if (flushThreshold >= 0 && writer.BytesWritten > flushThreshold)
+                if (flushThreshold >= 0 && writer.BytesPending > flushThreshold)
                 {
                     return false;
                 }
index 35d9586..3598e34 100644 (file)
@@ -7,7 +7,7 @@ namespace System.Text.Json.Serialization.Policies
     internal abstract class JsonValueConverter<TValue>
     {
         public abstract bool TryRead(Type valueType, ref Utf8JsonReader reader, out TValue value);
-        public abstract void Write(TValue value, ref Utf8JsonWriter writer);
-        public abstract void Write(Span<byte> escapedPropertyName, TValue value, ref Utf8JsonWriter writer);
+        public abstract void Write(TValue value, Utf8JsonWriter writer);
+        public abstract void Write(Span<byte> escapedPropertyName, TValue value, Utf8JsonWriter writer);
     }
 }
@@ -7,21 +7,23 @@ using System.Diagnostics;
 
 namespace System.Text.Json.Serialization
 {
-    // Note: this is currently an internal class that will be replaced with a shared version.
-    internal sealed class ArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
+    /// <summary>
+    ///   This is an implementation detail and MUST NOT be called by source-package consumers.
+    /// </summary>
+    internal sealed class PooledBufferWriter<T> : IBufferWriter<T>, IDisposable
     {
         private T[] _rentedBuffer;
         private int _index;
 
         private const int MinimumBufferSize = 256;
 
-        public ArrayBufferWriter()
+        public PooledBufferWriter()
         {
             _rentedBuffer = ArrayPool<T>.Shared.Rent(MinimumBufferSize);
             _index = 0;
         }
 
-        public ArrayBufferWriter(int initialCapacity)
+        public PooledBufferWriter(int initialCapacity)
         {
             if (initialCapacity <= 0)
                 throw new ArgumentException(nameof(initialCapacity));
@@ -101,7 +103,7 @@ namespace System.Text.Json.Serialization
         private void CheckIfDisposed()
         {
             if (_rentedBuffer == null)
-                ThrowHelper.ThrowObjectDisposedException(nameof(ArrayBufferWriter<T>));
+                ThrowHelper.ThrowObjectDisposedException(nameof(PooledBufferWriter<T>));
         }
 
         public void Advance(int count)
index cdbd7a4..4d9abbd 100644 (file)
@@ -45,17 +45,9 @@ namespace System.Text.Json
             throw GetArgumentException(SR.SpecialNumberValuesNotSupported);
         }
 
-        public static void ThrowArgumentException(ExceptionResource resource, int minimumSize = 0)
+        public static void ThrowInvalidOperationException_NeedLargerSpan()
         {
-            if (resource == ExceptionResource.FailedToGetLargerSpan)
-            {
-                throw GetArgumentException(SR.FailedToGetLargerSpan);
-            }
-            else
-            {
-                Debug.Assert(resource == ExceptionResource.FailedToGetMinimumSizeSpan);
-                throw GetArgumentException(SR.Format(SR.FailedToGetMinimumSizeSpan, minimumSize));
-            }
+            throw GetInvalidOperationException(SR.FailedToGetLargerSpan);
         }
 
         public static void ThrowArgumentException(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> value)
@@ -352,6 +344,11 @@ namespace System.Text.Json
             throw GetInvalidOperationException(resource, currentDepth, token, tokenType);
         }
 
+        public static void ThrowArgumentException_InvalidCommentValue()
+        {
+            throw new ArgumentException(SR.CannotWriteCommentWithEmbeddedDelimiter);
+        }
+
         public static void ThrowArgumentException_InvalidUTF8(ReadOnlySpan<byte> value)
         {
             var builder = new StringBuilder();
@@ -532,8 +529,6 @@ namespace System.Text.Json
         CannotStartObjectArrayAfterPrimitiveOrClose,
         CannotWriteValueWithinObject,
         CannotWriteValueAfterPrimitive,
-        FailedToGetMinimumSizeSpan,
-        FailedToGetLargerSpan,
         CannotWritePropertyWithinArray,
         ExpectedJsonTokens,
         TrailingCommaNotAllowedBeforeArrayEnd,
index c87795c..82b67a4 100644 (file)
@@ -9,40 +9,25 @@ namespace System.Text.Json
 {
     internal static partial class JsonWriterHelper
     {
-        public static bool TryWriteIndentation(Span<byte> buffer, int indent, out int bytesWritten)
+        public static void WriteIndentation(Span<byte> buffer, int indent)
         {
             Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0);
+            Debug.Assert(buffer.Length >= indent);
 
-            if (buffer.Length >= indent)
-            {
-                // Based on perf tests, the break-even point where vectorized Fill is faster
-                // than explicitly writing the space in a loop is 8.
-                if (indent < 8)
-                {
-                    int i = 0;
-                    while (i < indent)
-                    {
-                        buffer[i++] = JsonConstants.Space;
-                        buffer[i++] = JsonConstants.Space;
-                    }
-                }
-                else
-                {
-                    buffer.Slice(0, indent).Fill(JsonConstants.Space);
-                }
-                bytesWritten = indent;
-                return true;
-            }
-            else
+            // Based on perf tests, the break-even point where vectorized Fill is faster
+            // than explicitly writing the space in a loop is 8.
+            if (indent < 8)
             {
                 int i = 0;
-                while (i < buffer.Length - 1)
+                while (i < indent)
                 {
                     buffer[i++] = JsonConstants.Space;
                     buffer[i++] = JsonConstants.Space;
                 }
-                bytesWritten = i;
-                return false;
+            }
+            else
+            {
+                buffer.Slice(0, indent).Fill(JsonConstants.Space);
             }
         }
 
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs
deleted file mode 100644 (file)
index 52e98d4..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// Defines an opaque type that holds and saves all the relevant state information which must be provided
-    /// to the <see cref="Utf8JsonWriter"/> to continue writing after completing a partial write.
-    /// </summary>
-    /// <remarks>
-    /// This type is required to support reentrancy when writing incomplete data, and to continue
-    /// writing in chunks. Unlike the <see cref="Utf8JsonWriter"/>, which is a ref struct,
-    /// this type can survive across async/await boundaries and hence this type is required to provide
-    /// support for writing more JSON text asynchronously before continuing with a new instance of the <see cref="Utf8JsonWriter"/>.
-    /// </remarks>
-    public struct JsonWriterState
-    {
-        internal long _bytesWritten;
-        internal long _bytesCommitted;
-        internal bool _inObject;
-        internal bool _isNotPrimitive;
-        internal JsonTokenType _tokenType;
-        internal int _currentDepth;
-        internal JsonWriterOptions _writerOptions;
-        internal BitStack _bitStack;
-
-        /// <summary>
-        /// Returns the total amount of bytes written by the <see cref="Utf8JsonWriter"/> so far.
-        /// This includes data that has been written beyond what has already been committed.
-        /// </summary>
-        public long BytesWritten => _bytesWritten;
-
-        /// <summary>
-        /// Returns the total amount of bytes committed to the output by the <see cref="Utf8JsonWriter"/> so far.
-        /// This is how much the IBufferWriter has advanced.
-        /// </summary>
-        public long BytesCommitted => _bytesCommitted;
-
-        /// <summary>
-        /// Constructs a new <see cref="JsonWriterState"/> instance.
-        /// </summary>
-        /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonWriter"/>
-        /// By default, the <see cref="Utf8JsonWriter"/> writes JSON minimized (i.e. with no extra whitespace)
-        /// and validates that the JSON being written is structurally valid according to JSON RFC.</param>
-        /// <remarks>
-        /// An instance of this state must be passed to the <see cref="Utf8JsonWriter"/> ctor with the output destination.
-        /// Unlike the <see cref="Utf8JsonWriter"/>, which is a ref struct, the state can survive
-        /// across async/await boundaries and hence this type is required to provide support for reading
-        /// in more data asynchronously before continuing with a new instance of the <see cref="Utf8JsonWriter"/>.
-        /// </remarks>
-        public JsonWriterState(JsonWriterOptions options = default)
-        {
-            _bytesWritten = default;
-            _bytesCommitted = default;
-            _inObject = default;
-            _isNotPrimitive = default;
-            _tokenType = default;
-            _currentDepth = default;
-            _writerOptions = options;
-
-            // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
-            // This way we avoid allocations in the common, default cases, and allocate lazily.
-            _bitStack = default;
-        }
-
-        /// <summary>
-        /// Gets the custom behavior when writing JSON using
-        /// the <see cref="Utf8JsonWriter"/> which indicates whether to format the output
-        /// while writing and whether to skip structural JSON validation or not.
-        /// </summary>
-        public JsonWriterOptions Options => _writerOptions;
-    }
-}
index de9873b..065c72f 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="DateTime"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTime"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000.
         /// </remarks>
-        public void WriteString(string propertyName, DateTime value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), value, escape);
+        public void WriteString(string propertyName, DateTime value)
+            => WriteString(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="DateTime"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,18 +47,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTime"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<char> propertyName, DateTime value, bool escape = true)
+        public void WriteString(ReadOnlySpan<char> propertyName, DateTime value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(propertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(propertyName, value);
-            }
+            WriteStringEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -65,7 +62,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -75,18 +74,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTime"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTime value, bool escape = true)
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(utf8PropertyName, value);
-            }
+            WriteStringEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -96,7 +88,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -112,7 +104,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -132,21 +124,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -165,21 +147,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -193,7 +165,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTime value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -206,7 +178,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -218,76 +190,180 @@ namespace System.Text.Json
 
         private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, DateTime value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6;
 
-            WriteStringValue(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6);
 
-            WriteStringValue(value, ref idx);
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
 
-            Advance(idx);
-        }
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            Span<byte> output = _memory.Span;
 
-            WriteStringValue(value, ref idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
-        }
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            WriteStringValue(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
-        private void WriteStringValue(DateTime value, ref int idx)
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
         {
-            if (_buffer.Length <= idx)
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            FormatLoop(value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
-        }
 
-        private void FormatLoop(DateTime value, ref int idx)
-        {
-            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
             bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
             Debug.Assert(result);
-
             JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
+        {
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
 
-            if (_buffer.Length - idx < bytesWritten)
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, bytesWritten);
+                Grow(maxRequired);
             }
 
-            tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx));
+            Span<byte> output = _memory.Span;
 
-            idx += bytesWritten;
-        }
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-        private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O');
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
+        }
     }
 }
index 95cb857..02474bd 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="DateTimeOffset"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTimeOffset"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000-07:00.
         /// </remarks>
-        public void WriteString(string propertyName, DateTimeOffset value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), value, escape);
+        public void WriteString(string propertyName, DateTimeOffset value)
+            => WriteString(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="DateTimeOffset"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,18 +47,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTimeOffset"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000-07:00.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<char> propertyName, DateTimeOffset value, bool escape = true)
+        public void WriteString(ReadOnlySpan<char> propertyName, DateTimeOffset value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(propertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(propertyName, value);
-            }
+            WriteStringEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -65,7 +62,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -75,18 +74,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="DateTimeOffset"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000-07:00.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value, bool escape = true)
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(utf8PropertyName, value);
-            }
+            WriteStringEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -96,7 +88,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -112,7 +104,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -132,21 +124,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -165,21 +147,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -193,7 +165,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTimeOffset value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -206,7 +178,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -218,74 +190,180 @@ namespace System.Text.Json
 
         private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6;
 
-            WriteStringValue(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6);
 
-            WriteStringValue(value, ref idx);
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
 
-            Advance(idx);
-        }
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            Span<byte> output = _memory.Span;
 
-            WriteStringValue(value, ref idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
-        }
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            WriteStringValue(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
-        private void WriteStringValue(DateTimeOffset value, ref int idx)
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
         {
-            if (_buffer.Length <= idx)
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            FormatLoop(value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
-        }
 
-        private void FormatLoop(DateTimeOffset value, ref int idx)
-        {
-            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
 
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
             bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
             Debug.Assert(result);
-
             JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
+        {
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
+
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length - idx < bytesWritten)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx, bytesWritten);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx));
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
 
-            idx += bytesWritten;
+            output[BytesPending++] = JsonConstants.Quote;
         }
     }
 }
index f03e84d..be3a8f5 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="decimal"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="decimal"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(string propertyName, decimal value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), value, escape);
+        public void WriteNumber(string propertyName, decimal value)
+            => WriteNumber(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="decimal"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,18 +47,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="decimal"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<char> propertyName, decimal value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<char> propertyName, decimal value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(propertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(propertyName, value);
-            }
+            WriteNumberEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -65,7 +62,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -75,18 +74,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="decimal"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, decimal value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, decimal value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(utf8PropertyName, value);
-            }
+            WriteNumberEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -96,7 +88,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -112,7 +104,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -132,21 +124,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -165,21 +147,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -193,7 +165,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, decimal value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -206,7 +178,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, decimal value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -218,49 +190,152 @@ namespace System.Text.Json
 
         private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, decimal value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDecimalLength - 4);
+
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 4;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, decimal value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDecimalLength - 4);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);
 
-            Advance(idx);
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, decimal value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteNumberValueFormatLoop(decimal value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDecimalLength);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                Grow(maxRequired);
             }
-            idx += bytesWritten;
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index ea19ce3..c8c67ad 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="double"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(string propertyName, double value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), value, escape);
+        public void WriteNumber(string propertyName, double value)
+            => WriteNumber(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="double"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,19 +47,12 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<char> propertyName, double value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<char> propertyName, double value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
             JsonWriterHelper.ValidateDouble(value);
 
-            if (escape)
-            {
-                WriteNumberEscape(propertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(propertyName, value);
-            }
+            WriteNumberEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -66,7 +63,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -76,19 +75,12 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, double value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, double value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
             JsonWriterHelper.ValidateDouble(value);
 
-            if (escape)
-            {
-                WriteNumberEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(utf8PropertyName, value);
-            }
+            WriteNumberEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -98,7 +90,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -114,7 +106,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -134,21 +126,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -167,21 +149,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -195,7 +167,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, double value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -208,7 +180,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, double value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -220,49 +192,152 @@ namespace System.Text.Json
 
         private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, double value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDoubleLength - 4);
+
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 4;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, double value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDoubleLength - 4);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, double value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength);
 
-            Advance(idx);
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, double value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteNumberValueFormatLoop(double value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDoubleLength);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                Grow(maxRequired);
             }
-            idx += bytesWritten;
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index 5853265..7595cd4 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="float"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(string propertyName, float value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), value, escape);
+        public void WriteNumber(string propertyName, float value)
+            => WriteNumber(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="float"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,19 +47,12 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<char> propertyName, float value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<char> propertyName, float value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
             JsonWriterHelper.ValidateSingle(value);
 
-            if (escape)
-            {
-                WriteNumberEscape(propertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(propertyName, value);
-            }
+            WriteNumberEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -66,7 +63,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -76,19 +75,12 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, float value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, float value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
             JsonWriterHelper.ValidateSingle(value);
 
-            if (escape)
-            {
-                WriteNumberEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(utf8PropertyName, value);
-            }
+            WriteNumberEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -98,7 +90,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -114,7 +106,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -134,21 +126,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -167,21 +149,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -195,7 +167,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, float value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -205,64 +177,167 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteNumberByOptions(ReadOnlySpan<byte> propertyName, float value)
+        private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, float value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
-                WriteNumberIndented(propertyName, value);
+                WriteNumberIndented(utf8PropertyName, value);
             }
             else
             {
-                WriteNumberMinimized(propertyName, value);
+                WriteNumberMinimized(utf8PropertyName, value);
             }
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, float value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatSingleLength - 4);
+
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 4;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, float value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatSingleLength - 4);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, float value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, float value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteNumberValueFormatLoop(float value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatSingleLength);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                WriteNewLine(output);
             }
-            idx += bytesWritten;
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index 23233e0..f7af6fc 100644 (file)
@@ -3,18 +3,20 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
-using System.Buffers.Text;
 using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="utf8FormattedNumber">The value to be written as a JSON number as part of the name/value pair.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -30,9 +32,11 @@ namespace System.Text.Json
         internal void WriteNumber(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8FormattedNumber)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
+            JsonWriterHelper.ValidateValue(utf8FormattedNumber);
             JsonWriterHelper.ValidateNumber(utf8FormattedNumber);
 
             WriteNumberEscape(propertyName, utf8FormattedNumber);
+
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
         }
@@ -42,6 +46,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="utf8FormattedNumber">The value to be written as a JSON number as part of the name/value pair.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -57,9 +64,11 @@ namespace System.Text.Json
         internal void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8FormattedNumber)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
+            JsonWriterHelper.ValidateValue(utf8FormattedNumber);
             JsonWriterHelper.ValidateNumber(utf8FormattedNumber);
 
             WriteNumberEscape(utf8PropertyName, utf8FormattedNumber);
+
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
         }
@@ -68,7 +77,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -84,7 +93,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -104,21 +113,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -137,21 +136,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -165,74 +154,27 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
-                WriteNumberIndented(propertyName, value);
+                WriteLiteralIndented(propertyName, value);
             }
             else
             {
-                WriteNumberMinimized(propertyName, value);
+                WriteLiteralMinimized(propertyName, value);
             }
         }
 
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
-                WriteNumberIndented(utf8PropertyName, value);
+                WriteLiteralIndented(utf8PropertyName, value);
             }
             else
             {
-                WriteNumberMinimized(utf8PropertyName, value);
-            }
-        }
-
-        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
-        {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
-
-            WriteNumberValueFormatLoop(value, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
-        {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
-
-            WriteNumberValueFormatLoop(value, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
-
-            WriteNumberValueFormatLoop(value, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
-
-            WriteNumberValueFormatLoop(value, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteNumberValueFormatLoop(ReadOnlySpan<byte> value, ref int idx)
-        {
-            if (_buffer.Length - idx - value.Length < 0)
-            {
-                AdvanceAndGrow(ref idx, value.Length);
+                WriteLiteralMinimized(utf8PropertyName, value);
             }
-
-            value.CopyTo(_buffer.Slice(idx));
-            idx += value.Length;
         }
     }
 }
index c201b6a..d1e4c69 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="Guid"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="Guid"/> using the default <see cref="StandardFormat"/> (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn.
         /// </remarks>
-        public void WriteString(string propertyName, Guid value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), value, escape);
+        public void WriteString(string propertyName, Guid value)
+            => WriteString(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="Guid"/> value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,18 +47,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="Guid"/> using the default <see cref="StandardFormat"/> (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<char> propertyName, Guid value, bool escape = true)
+        public void WriteString(ReadOnlySpan<char> propertyName, Guid value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(propertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(propertyName, value);
-            }
+            WriteStringEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -65,7 +62,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -75,18 +74,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="Guid"/> using the default <see cref="StandardFormat"/> (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn.
         /// </remarks>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, Guid value, bool escape = true)
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, Guid value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteStringByOptions(utf8PropertyName, value);
-            }
+            WriteStringEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -96,7 +88,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -112,7 +104,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -132,21 +124,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -165,21 +147,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
@@ -193,7 +165,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, Guid value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -206,7 +178,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, Guid value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -218,66 +190,168 @@ namespace System.Text.Json
 
         private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, Guid value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatGuidLength - 6);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 6;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            WriteStringValue(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, Guid value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatGuidLength - 6);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            WriteStringValue(value, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, Guid value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            WriteStringValue(value, ref idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-            Advance(idx);
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, Guid value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteStringValue(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteStringValue(Guid value, ref int idx)
-        {
-            if (_buffer.Length <= idx)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            FormatLoop(value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
-        }
 
-        private void FormatLoop(Guid value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatGuidLength);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                WriteNewLine(output);
             }
-            idx += bytesWritten;
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
     }
 }
index 9c1867c..4d6f04d 100644 (file)
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void ValidatePropertyNameAndDepth(ReadOnlySpan<char> propertyName)
@@ -28,7 +28,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void ValidateWritingProperty()
         {
-            if (!_writerOptions.SkipValidation)
+            if (!Options.SkipValidation)
             {
                 if (!_inObject)
                 {
@@ -41,7 +41,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void ValidateWritingProperty(byte token)
         {
-            if (!_writerOptions.SkipValidation)
+            if (!Options.SkipValidation)
             {
                 if (!_inObject)
                 {
@@ -52,212 +52,154 @@ namespace System.Text.Json
             }
         }
 
-        private int WritePropertyNameMinimized(ReadOnlySpan<byte> escapedPropertyName)
+        private void WritePropertyNameMinimized(ReadOnlySpan<byte> escapedPropertyName, byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
-            }
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - 5);
 
-            if (_buffer.Length <= idx)
+            int minRequired = escapedPropertyName.Length + 4; // 2 quotes, 1 colon, and 1 start token
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            CopyLoop(escapedPropertyName, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.Quote;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            return idx;
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = token;
         }
 
-        private int WritePropertyNameIndented(ReadOnlySpan<byte> escapedPropertyName)
+        private void WritePropertyNameIndented(ReadOnlySpan<byte> escapedPropertyName, byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
-            }
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            if (_tokenType != JsonTokenType.None)
-                WriteNewLine(ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - s_newLineLength);
 
-            int indent = Indentation;
-            while (true)
-            {
-                bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                idx += bytesWritten;
-                if (result)
-                {
-                    break;
-                }
-                indent -= bytesWritten;
-                AdvanceAndGrow(ref idx);
-            }
+            int minRequired = indent + escapedPropertyName.Length + 5; // 2 quotes, 1 colon, 1 space, and 1 start token
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-            if (_buffer.Length <= idx)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            CopyLoop(escapedPropertyName, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            if (_buffer.Length <= idx)
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx);
+                WriteNewLine(output);
             }
-            _buffer[idx++] = JsonConstants.KeyValueSeperator;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Space;
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            return idx;
+            output[BytesPending++] = JsonConstants.Quote;
+
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+            output[BytesPending++] = token;
         }
 
-        private int WritePropertyNameMinimized(ReadOnlySpan<char> escapedPropertyName)
+        private void WritePropertyNameMinimized(ReadOnlySpan<char> escapedPropertyName, byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
-            }
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 5);
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Quote;
+            // All ASCII, 2 quotes, 1 colon, and 1 start token => escapedPropertyName.Length + 4
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 5;
 
-            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(escapedPropertyName);
-            int partialConsumed = 0;
-            while (true)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written);
-                idx += written;
-                if (status == OperationStatus.Done)
-                {
-                    break;
-                }
-                partialConsumed += consumed;
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Quote;
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Quote;
 
-            return idx;
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = token;
         }
 
-        private int WritePropertyNameIndented(ReadOnlySpan<char> escapedPropertyName)
+        private void WritePropertyNameIndented(ReadOnlySpan<char> escapedPropertyName, byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
-            }
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            if (_tokenType != JsonTokenType.None)
-                WriteNewLine(ref idx);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - s_newLineLength);
 
-            int indent = Indentation;
-            while (true)
-            {
-                bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                idx += bytesWritten;
-                if (result)
-                {
-                    break;
-                }
-                indent -= bytesWritten;
-                AdvanceAndGrow(ref idx);
-            }
+            // All ASCII, 2 quotes, 1 colon, 1 space, and 1 start token => indent + escapedPropertyName.Length + 5 
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6 + s_newLineLength;
 
-            if (_buffer.Length <= idx)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(escapedPropertyName);
-            int partialConsumed = 0;
-            while (true)
-            {
-                OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written);
-                idx += written;
-                if (status == OperationStatus.Done)
-                {
-                    break;
-                }
-                partialConsumed += consumed;
-                AdvanceAndGrow(ref idx);
-            }
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            if (_buffer.Length <= idx)
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx);
+                WriteNewLine(output);
             }
-            _buffer[idx++] = JsonConstants.KeyValueSeperator;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Space;
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            return idx;
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+            output[BytesPending++] = token;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void TranscodeAndWrite(ReadOnlySpan<char> escapedPropertyName, Span<byte> output)
+        {
+            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(escapedPropertyName);
+            OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int consumed, out int written);
+            Debug.Assert(status == OperationStatus.Done);
+            Debug.Assert(consumed == byteSpan.Length);
+            BytesPending += written;
         }
     }
 }
index 13f3a16..f5e2a14 100644 (file)
@@ -7,47 +7,44 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteNull(string propertyName, bool escape = true)
-            => WriteNull(propertyName.AsSpan(), escape);
+        public void WriteNull(string propertyName)
+            => WriteNull(propertyName.AsSpan());
 
         /// <summary>
         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteNull(ReadOnlySpan<char> propertyName, bool escape = true)
+        public void WriteNull(ReadOnlySpan<char> propertyName)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
             ReadOnlySpan<byte> span = JsonConstants.NullValue;
 
-            if (escape)
-            {
-                WriteLiteralEscape(propertyName, span);
-            }
-            else
-            {
-                WriteLiteralByOptions(propertyName, span);
-            }
+            WriteLiteralEscape(propertyName, span);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Null;
@@ -57,27 +54,22 @@ namespace System.Text.Json
         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteNull(ReadOnlySpan<byte> utf8PropertyName, bool escape = true)
+        public void WriteNull(ReadOnlySpan<byte> utf8PropertyName)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
             ReadOnlySpan<byte> span = JsonConstants.NullValue;
 
-            if (escape)
-            {
-                WriteLiteralEscape(utf8PropertyName, span);
-            }
-            else
-            {
-                WriteLiteralByOptions(utf8PropertyName, span);
-            }
+            WriteLiteralEscape(utf8PropertyName, span);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Null;
@@ -88,42 +80,39 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON literal "true" or "false" as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteBoolean(string propertyName, bool value, bool escape = true)
-            => WriteBoolean(propertyName.AsSpan(), value, escape);
+        public void WriteBoolean(string propertyName, bool value)
+            => WriteBoolean(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="bool"/> value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON literal "true" or "false" as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteBoolean(ReadOnlySpan<char> propertyName, bool value, bool escape = true)
+        public void WriteBoolean(ReadOnlySpan<char> propertyName, bool value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
             ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
 
-            if (escape)
-            {
-                WriteLiteralEscape(propertyName, span);
-            }
-            else
-            {
-                WriteLiteralByOptions(propertyName, span);
-            }
+            WriteLiteralEscape(propertyName, span);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
@@ -134,27 +123,22 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON literal "true" or "false" as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteBoolean(ReadOnlySpan<byte> utf8PropertyName, bool value, bool escape = true)
+        public void WriteBoolean(ReadOnlySpan<byte> utf8PropertyName, bool value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
             ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
 
-            if (escape)
-            {
-                WriteLiteralEscape(utf8PropertyName, span);
-            }
-            else
-            {
-                WriteLiteralByOptions(utf8PropertyName, span);
-            }
+            WriteLiteralEscape(utf8PropertyName, span);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
@@ -164,7 +148,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -180,7 +164,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -200,21 +184,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
@@ -233,21 +207,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
@@ -261,49 +225,177 @@ namespace System.Text.Json
         private void WriteLiteralByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            int idx;
-            if (_writerOptions.Indented)
+            if (Options.Indented)
+            {
+                WriteLiteralIndented(propertyName, value);
+            }
+            else
+            {
+                WriteLiteralMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
+        {
+            ValidateWritingProperty();
+            if (Options.Indented)
             {
-                idx = WritePropertyNameIndented(propertyName);
+                WriteLiteralIndented(utf8PropertyName, value);
             }
             else
             {
-                idx = WritePropertyNameMinimized(propertyName);
+                WriteLiteralMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteLiteralMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
+        {
+            Debug.Assert(value.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - value.Length - 4);
+
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + value.Length + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 4;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
             }
 
-            if (value.Length > _buffer.Length - idx)
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx, value.Length);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            value.CopyTo(_buffer.Slice(idx));
-            idx += value.Length;
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            value.CopyTo(output.Slice(BytesPending));
+            BytesPending += value.Length;
         }
 
-        private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
+        private void WriteLiteralMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
         {
-            ValidateWritingProperty();
-            int idx;
-            if (_writerOptions.Indented)
+            Debug.Assert(value.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - value.Length - 4);
+
+            int minRequired = escapedPropertyName.Length + value.Length + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                idx = WritePropertyNameIndented(utf8PropertyName);
+                Grow(maxRequired);
             }
-            else
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
             {
-                idx = WritePropertyNameMinimized(utf8PropertyName);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            if (value.Length > _buffer.Length - idx)
+            value.CopyTo(output.Slice(BytesPending));
+            BytesPending += value.Length;
+        }
+
+        private void WriteLiteralIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
+        {
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(value.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + value.Length + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, value.Length);
+                Grow(maxRequired);
             }
 
-            value.CopyTo(_buffer.Slice(idx));
-            idx += value.Length;
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            value.CopyTo(output.Slice(BytesPending));
+            BytesPending += value.Length;
+        }
+
+        private void WriteLiteralIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
+        {
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(value.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength);
+
+            int minRequired = indent + escapedPropertyName.Length + value.Length + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
 
-            Advance(idx);
+            value.CopyTo(output.Slice(BytesPending));
+            BytesPending += value.Length;
         }
     }
 }
index be106a1..8ab3243 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="long"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -25,15 +27,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="long"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(string propertyName, long value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), value, escape);
+        public void WriteNumber(string propertyName, long value)
+            => WriteNumber(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="long"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -43,18 +47,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="long"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<char> propertyName, long value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<char> propertyName, long value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(propertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(propertyName, value);
-            }
+            WriteNumberEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -65,7 +62,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -75,18 +74,11 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="long"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, long value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, long value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(utf8PropertyName, value);
-            }
+            WriteNumberEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -97,7 +89,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -107,15 +101,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="int"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(string propertyName, int value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), (long)value, escape);
+        public void WriteNumber(string propertyName, int value)
+            => WriteNumber(propertyName.AsSpan(), (long)value);
 
         /// <summary>
         /// Writes the property name and <see cref="int"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -125,15 +121,17 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="int"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<char> propertyName, int value, bool escape = true)
-            => WriteNumber(propertyName, (long)value, escape);
+        public void WriteNumber(ReadOnlySpan<char> propertyName, int value)
+            => WriteNumber(propertyName, (long)value);
 
         /// <summary>
         /// Writes the property name and <see cref="int"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -143,14 +141,14 @@ namespace System.Text.Json
         /// <remarks>
         /// Writes the <see cref="int"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, int value, bool escape = true)
-            => WriteNumber(utf8PropertyName, (long)value, escape);
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, int value)
+            => WriteNumber(utf8PropertyName, (long)value);
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, long value)
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -166,7 +164,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -186,21 +184,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -219,21 +207,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -247,7 +225,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, long value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -260,7 +238,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, long value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -272,49 +250,152 @@ namespace System.Text.Json
 
         private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, long value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatInt64Length - 4);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 4;
 
-            Advance(idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, long value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatInt64Length - 4);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, long value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, long value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteNumberValueFormatLoop(long value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatInt64Length);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                Grow(maxRequired);
             }
-            idx += bytesWritten;
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index f90c35b..1036779 100644 (file)
@@ -9,49 +9,44 @@ using System.Runtime.InteropServices;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(string propertyName, string value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), value.AsSpan(), escape);
+        public void WriteString(string propertyName, string value)
+            => WriteString(propertyName.AsSpan(), value.AsSpan());
 
         /// <summary>
         /// Writes the UTF-16 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value, bool escape = true)
+        public void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
         {
             JsonWriterHelper.ValidatePropertyAndValue(propertyName, value);
 
-            if (escape)
-            {
-                WriteStringEscape(propertyName, value);
-            }
-            else
-            {
-                WriteStringDontEscape(propertyName, value);
-            }
+            WriteStringEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -62,26 +57,20 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value, bool escape = true)
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
         {
             JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, utf8Value);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8PropertyName, utf8Value);
-            }
-            else
-            {
-                WriteStringDontEscape(utf8PropertyName, utf8Value);
-            }
+            WriteStringEscape(utf8PropertyName, utf8Value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -92,42 +81,37 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(string propertyName, ReadOnlySpan<char> value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), value, escape);
+        public void WriteString(string propertyName, ReadOnlySpan<char> value)
+            => WriteString(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the UTF-8 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value, bool escape = true)
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
         {
             JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, value);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteStringDontEscape(utf8PropertyName, value);
-            }
+            WriteStringEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -138,42 +122,37 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(string propertyName, ReadOnlySpan<byte> utf8Value, bool escape = true)
-            => WriteString(propertyName.AsSpan(), utf8Value, escape);
+        public void WriteString(string propertyName, ReadOnlySpan<byte> utf8Value)
+            => WriteString(propertyName.AsSpan(), utf8Value);
 
         /// <summary>
         /// Writes the UTF-16 property name and UTF-8 text value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value, bool escape = true)
+        public void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
         {
             JsonWriterHelper.ValidatePropertyAndValue(propertyName, utf8Value);
 
-            if (escape)
-            {
-                WriteStringEscape(propertyName, utf8Value);
-            }
-            else
-            {
-                WriteStringDontEscape(propertyName, utf8Value);
-            }
+            WriteStringEscape(propertyName, utf8Value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -184,88 +163,34 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<char> propertyName, string value, bool escape = true)
-            => WriteString(propertyName, value.AsSpan(), escape);
+        public void WriteString(ReadOnlySpan<char> propertyName, string value)
+            => WriteString(propertyName, value.AsSpan());
 
         /// <summary>
         /// Writes the UTF-8 property name and string text value (as a JSON string) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.
-        /// The value is always escaped</param>
+        /// <remarks>
+        /// The property name and value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name or value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, string value, bool escape = true)
-            => WriteString(utf8PropertyName, value.AsSpan(), escape);
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void WriteStringDontEscape(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
-            if (valueIdx != -1)
-            {
-                WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx);
-            }
-            else
-            {
-                WriteStringByOptions(escapedPropertyName, value);
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void WriteStringDontEscape(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> utf8Value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
-            if (valueIdx != -1)
-            {
-                WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx);
-            }
-            else
-            {
-                WriteStringByOptions(escapedPropertyName, utf8Value);
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void WriteStringDontEscape(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> utf8Value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
-            if (valueIdx != -1)
-            {
-                WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx);
-            }
-            else
-            {
-                WriteStringByOptions(escapedPropertyName, utf8Value);
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void WriteStringDontEscape(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
-            if (valueIdx != -1)
-            {
-                WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx);
-            }
-            else
-            {
-                WriteStringByOptions(escapedPropertyName, value);
-            }
-        }
+        public void WriteString(ReadOnlySpan<byte> utf8PropertyName, string value)
+            => WriteString(utf8PropertyName, value.AsSpan());
 
         private void WriteStringEscapeValueOnly(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> value, int firstEscapeIndex)
         {
@@ -328,8 +253,8 @@ namespace System.Text.Json
             int valueIdx = JsonWriterHelper.NeedsEscaping(value);
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
 
             // Equivalent to: valueIdx != -1 || propertyIdx != -1
             if (valueIdx + propertyIdx != -2)
@@ -347,8 +272,8 @@ namespace System.Text.Json
             int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
 
             // Equivalent to: valueIdx != -1 || propertyIdx != -1
             if (valueIdx + propertyIdx != -2)
@@ -366,8 +291,8 @@ namespace System.Text.Json
             int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
 
             // Equivalent to: valueIdx != -1 || propertyIdx != -1
             if (valueIdx + propertyIdx != -2)
@@ -385,8 +310,8 @@ namespace System.Text.Json
             int valueIdx = JsonWriterHelper.NeedsEscaping(value);
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
 
             // Equivalent to: valueIdx != -1 || propertyIdx != -1
             if (valueIdx + propertyIdx != -2)
@@ -412,20 +337,21 @@ namespace System.Text.Json
                 int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal);
 
                 Span<char> escapedValue;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     valueArray = ArrayPool<char>.Shared.Rent(length);
                     escapedValue = valueArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         char* ptr = stackalloc char[length];
                         escapedValue = new Span<char>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
                 value = escapedValue.Slice(0, written);
             }
@@ -435,20 +361,21 @@ namespace System.Text.Json
                 int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
 
                 Span<char> escapedPropertyName;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     propertyArray = ArrayPool<char>.Shared.Rent(length);
                     escapedPropertyName = propertyArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         char* ptr = stackalloc char[length];
                         escapedPropertyName = new Span<char>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
                 propertyName = escapedPropertyName.Slice(0, written);
             }
@@ -479,20 +406,21 @@ namespace System.Text.Json
                 int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
 
                 Span<byte> escapedValue;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     valueArray = ArrayPool<byte>.Shared.Rent(length);
                     escapedValue = valueArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         byte* ptr = stackalloc byte[length];
                         escapedValue = new Span<byte>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
                 utf8Value = escapedValue.Slice(0, written);
             }
@@ -500,21 +428,23 @@ namespace System.Text.Json
             if (firstEscapeIndexProp != -1)
             {
                 int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
+
                 Span<byte> escapedPropertyName;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     propertyArray = ArrayPool<byte>.Shared.Rent(length);
                     escapedPropertyName = propertyArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         byte* ptr = stackalloc byte[length];
                         escapedPropertyName = new Span<byte>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
                 utf8PropertyName = escapedPropertyName.Slice(0, written);
             }
@@ -545,20 +475,21 @@ namespace System.Text.Json
                 int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
 
                 Span<byte> escapedValue;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     valueArray = ArrayPool<byte>.Shared.Rent(length);
                     escapedValue = valueArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         byte* ptr = stackalloc byte[length];
                         escapedValue = new Span<byte>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
                 utf8Value = escapedValue.Slice(0, written);
             }
@@ -566,21 +497,23 @@ namespace System.Text.Json
             if (firstEscapeIndexProp != -1)
             {
                 int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
+
                 Span<char> escapedPropertyName;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     propertyArray = ArrayPool<char>.Shared.Rent(length);
                     escapedPropertyName = propertyArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         char* ptr = stackalloc char[length];
                         escapedPropertyName = new Span<char>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
                 propertyName = escapedPropertyName.Slice(0, written);
             }
@@ -611,20 +544,21 @@ namespace System.Text.Json
                 int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal);
 
                 Span<char> escapedValue;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     valueArray = ArrayPool<char>.Shared.Rent(length);
                     escapedValue = valueArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         char* ptr = stackalloc char[length];
                         escapedValue = new Span<char>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
                 value = escapedValue.Slice(0, written);
             }
@@ -632,21 +566,23 @@ namespace System.Text.Json
             if (firstEscapeIndexProp != -1)
             {
                 int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
+
                 Span<byte> escapedPropertyName;
-                if (length > StackallocThreshold)
+                if (length > JsonConstants.StackallocThreshold)
                 {
                     propertyArray = ArrayPool<byte>.Shared.Rent(length);
                     escapedPropertyName = propertyArray;
                 }
                 else
                 {
-                    // Cannot create a span directly since it gets passed to instance methods on a ref struct.
+                    // Cannot create a span directly since it gets assigned to parameter and passed down.
                     unsafe
                     {
                         byte* ptr = stackalloc byte[length];
                         escapedPropertyName = new Span<byte>(ptr, length);
                     }
                 }
+
                 JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
                 utf8PropertyName = escapedPropertyName.Slice(0, written);
             }
@@ -667,7 +603,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -680,7 +616,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, utf8Value);
             }
@@ -693,7 +629,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(propertyName, utf8Value);
             }
@@ -706,7 +642,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -716,122 +652,342 @@ namespace System.Text.Json
             }
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> escapedValue)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 6) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            WriteStringValue(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - escapedValue.Length - 6);
+
+            int minRequired = escapedPropertyName.Length + escapedValue.Length + 5; // 2 quotes for property name, 2 quotes for value, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            WriteStringValue(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 6;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            WriteStringValue(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> escapedValue)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 6;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
 
-            WriteStringValue(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> escapedValue)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length);
 
-            WriteStringValue(escapedValue, ref idx);
+            // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            Advance(idx);
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteStringValue(escapedValue, ref idx);
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + escapedValue.Length + 6; // 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            WriteStringValue(escapedValue, ref idx);
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
-        }
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> escapedValue)
-        {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            WriteStringValue(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
-        private void WriteStringValue(ReadOnlySpan<char> escapedValue, ref int idx)
+        // TODO: https://github.com/dotnet/corefx/issues/36958
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
         {
-            if (_buffer.Length <= idx)
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(escapedValue);
-            int partialConsumed = 0;
-            while (true)
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
             {
-                OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written);
-                idx += written;
-                if (status == OperationStatus.Done)
-                {
-                    break;
-                }
-                partialConsumed += consumed;
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            if (_buffer.Length <= idx)
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx);
+                WriteNewLine(output);
             }
-            _buffer[idx++] = JsonConstants.Quote;
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
-        private void WriteStringValue(ReadOnlySpan<byte> escapedValue, ref int idx)
+        // TODO: https://github.com/dotnet/corefx/issues/36958
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> escapedValue)
         {
-            if (_buffer.Length <= idx)
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 7 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Quote;
 
-            CopyLoop(escapedValue, ref idx);
+            Span<byte> output = _memory.Span;
 
-            if (_buffer.Length <= idx)
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = JsonConstants.Quote;
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
     }
 }
index ee85f7d..8ba0f3c 100644 (file)
@@ -8,14 +8,16 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the property name and <see cref="ulong"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -26,15 +28,17 @@ namespace System.Text.Json
         /// Writes the <see cref="ulong"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(string propertyName, ulong value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), value, escape);
+        public void WriteNumber(string propertyName, ulong value)
+            => WriteNumber(propertyName.AsSpan(), value);
 
         /// <summary>
         /// Writes the property name and <see cref="ulong"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -45,18 +49,11 @@ namespace System.Text.Json
         /// Writes the <see cref="ulong"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(ReadOnlySpan<char> propertyName, ulong value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<char> propertyName, ulong value)
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(propertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(propertyName, value);
-            }
+            WriteNumberEscape(propertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -67,7 +64,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -78,18 +77,11 @@ namespace System.Text.Json
         /// Writes the <see cref="ulong"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, ulong value, bool escape = true)
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, ulong value)
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteNumberEscape(utf8PropertyName, value);
-            }
-            else
-            {
-                WriteNumberByOptions(utf8PropertyName, value);
-            }
+            WriteNumberEscape(utf8PropertyName, value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.Number;
@@ -100,7 +92,9 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -111,15 +105,17 @@ namespace System.Text.Json
         /// Writes the <see cref="uint"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(string propertyName, uint value, bool escape = true)
-            => WriteNumber(propertyName.AsSpan(), (ulong)value, escape);
+        public void WriteNumber(string propertyName, uint value)
+            => WriteNumber(propertyName.AsSpan(), (ulong)value);
 
         /// <summary>
         /// Writes the property name and <see cref="uint"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -130,15 +126,17 @@ namespace System.Text.Json
         /// Writes the <see cref="uint"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(ReadOnlySpan<char> propertyName, uint value, bool escape = true)
-            => WriteNumber(propertyName, (ulong)value, escape);
+        public void WriteNumber(ReadOnlySpan<char> propertyName, uint value)
+            => WriteNumber(propertyName, (ulong)value);
 
         /// <summary>
         /// Writes the property name and <see cref="uint"/> value (as a JSON number) as part of a name/value pair of a JSON object.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
         /// <param name="value">The value to be written as a JSON number as part of the name/value pair.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -149,14 +147,14 @@ namespace System.Text.Json
         /// Writes the <see cref="uint"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
         /// </remarks>
         [CLSCompliant(false)]
-        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, uint value, bool escape = true)
-            => WriteNumber(utf8PropertyName, (ulong)value, escape);
+        public void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, uint value)
+            => WriteNumber(utf8PropertyName, (ulong)value);
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, ulong value)
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -172,7 +170,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -192,21 +190,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -225,21 +213,11 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
@@ -253,7 +231,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, ulong value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -266,7 +244,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, ulong value)
         {
             ValidateWritingProperty();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -278,49 +256,152 @@ namespace System.Text.Json
 
         private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, ulong value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatUInt64Length - 4);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 4;
 
-            Advance(idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedPropertyName, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, ulong value)
         {
-            int idx = WritePropertyNameMinimized(escapedPropertyName);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatUInt64Length - 4);
+
+            int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3; // 2 quotes for property name, and 1 colon
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
 
-            Advance(idx);
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, ulong value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength);
+
+            // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 5 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            TranscodeAndWrite(escapedPropertyName, output);
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, ulong value)
         {
-            int idx = WritePropertyNameIndented(escapedPropertyName);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength);
 
-            Advance(idx);
-        }
+            int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
-        private void WriteNumberValueFormatLoop(ulong value, ref int idx)
-        {
-            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatUInt64Length);
-                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
-                Debug.Assert(result);
+                Grow(maxRequired);
             }
-            idx += bytesWritten;
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedPropertyName.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedPropertyName.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
+            output[BytesPending++] = JsonConstants.KeyValueSeperator;
+            output[BytesPending++] = JsonConstants.Space;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index dc749fa..ede1918 100644 (file)
@@ -8,60 +8,49 @@ using System.Runtime.InteropServices;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
+        private static char[] s_singleLineCommentDelimiter = new char[2] { '*', '/' };
+        private static ReadOnlySpan<byte> SingleLineCommentDelimiterUtf8 => new byte[2] { (byte)'*', (byte)'/' };
+
         /// <summary>
         /// Writes the string text value (as a JSON comment).
         /// </summary>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The comment value is not escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
-        /// Thrown when the specified value is too large.
+        /// Thrown when the specified value is too large OR if the given string text value contains a comment delimiter (i.e. */).
         /// </exception>
-        public void WriteCommentValue(string value, bool escape = true)
-            => WriteCommentValue(value.AsSpan(), escape);
+        public void WriteCommentValue(string value)
+            => WriteCommentValue(value.AsSpan());
 
         /// <summary>
         /// Writes the UTF-16 text value (as a JSON comment).
         /// </summary>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The comment value is not escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
-        /// Thrown when the specified value is too large.
+        /// Thrown when the specified value is too large OR if the given UTF-16 text value contains a comment delimiter (i.e. */).
         /// </exception>
-        public void WriteCommentValue(ReadOnlySpan<char> value, bool escape = true)
+        public void WriteCommentValue(ReadOnlySpan<char> value)
         {
             JsonWriterHelper.ValidateValue(value);
 
-            if (escape)
+            if (value.IndexOf(s_singleLineCommentDelimiter) != -1)
             {
-                WriteCommentEscape(value);
+                ThrowHelper.ThrowArgumentException_InvalidCommentValue();
             }
-            else
-            {
-                WriteCommentByOptions(value);
-            }
-        }
 
-        private void WriteCommentEscape(ReadOnlySpan<char> value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
-
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-
-            if (valueIdx != -1)
-            {
-                WriteCommentEscapeValue(value, valueIdx);
-            }
-            else
-            {
-                WriteCommentByOptions(value);
-            }
+            WriteCommentByOptions(value);
         }
 
         private void WriteCommentByOptions(ReadOnlySpan<char> value)
         {
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteCommentIndented(value);
             }
@@ -71,115 +60,96 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteCommentMinimized(ReadOnlySpan<char> escapedValue)
-        {
-            int idx = 0;
-
-            WriteCommentValue(escapedValue, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteCommentIndented(ReadOnlySpan<char> escapedValue)
+        private void WriteCommentMinimized(ReadOnlySpan<char> value)
         {
-            int idx = 0;
+            Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 4);
 
-            if (_tokenType != JsonTokenType.None)
-                WriteNewLine(ref idx);
+            // All ASCII, /*...*/ => escapedValue.Length + 4
+            // Optionally, up to 3x growth when transcoding
+            int maxRequired = (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4;
 
-            int indent = Indentation;
-            while (true)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                idx += bytesWritten;
-                if (result)
-                {
-                    break;
-                }
-                indent -= bytesWritten;
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
 
-            WriteCommentValue(escapedValue, ref idx);
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Slash;
+            output[BytesPending++] = JsonConstants.Asterisk;
+
+            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(value);
+            OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written);
+            Debug.Assert(status != OperationStatus.DestinationTooSmall);
+            BytesPending += written;
+
+            output[BytesPending++] = JsonConstants.Asterisk;
+            output[BytesPending++] = JsonConstants.Slash;
         }
 
-        private void WriteCommentEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal)
+        private void WriteCommentIndented(ReadOnlySpan<char> value)
         {
-            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
-            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length);
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            char[] valueArray = null;
+            Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - s_newLineLength);
 
-            int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal);
+            // All ASCII, /*...*/ => escapedValue.Length + 4
+            // Optionally, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + s_newLineLength;
 
-            Span<char> escapedValue;
-            if (length > StackallocThreshold)
-            {
-                valueArray = ArrayPool<char>.Shared.Rent(length);
-                escapedValue = valueArray;
-            }
-            else
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedValue = new Span<char>(ptr, length);
-                }
+                Grow(maxRequired);
             }
-            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
 
-            WriteCommentByOptions(escapedValue.Slice(0, written));
+            Span<byte> output = _memory.Span;
 
-            if (valueArray != null)
+            if (_tokenType != JsonTokenType.None)
             {
-                ArrayPool<char>.Shared.Return(valueArray);
+                WriteNewLine(output);
             }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Slash;
+            output[BytesPending++] = JsonConstants.Asterisk;
+
+            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(value);
+            OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written);
+            Debug.Assert(status != OperationStatus.DestinationTooSmall);
+            BytesPending += written;
+
+            output[BytesPending++] = JsonConstants.Asterisk;
+            output[BytesPending++] = JsonConstants.Slash;
         }
 
         /// <summary>
         /// Writes the UTF-8 text value (as a JSON comment).
         /// </summary>
         /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON comment within /*..*/.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The comment value is not escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
-        /// Thrown when the specified value is too large.
+        /// Thrown when the specified value is too large OR if the given UTF-8 text value contains a comment delimiter (i.e. */).
         /// </exception>
-        public void WriteCommentValue(ReadOnlySpan<byte> utf8Value, bool escape = true)
+        public void WriteCommentValue(ReadOnlySpan<byte> utf8Value)
         {
             JsonWriterHelper.ValidateValue(utf8Value);
 
-            if (escape)
+            if (utf8Value.IndexOf(SingleLineCommentDelimiterUtf8) != -1)
             {
-                WriteCommentEscape(utf8Value);
+                ThrowHelper.ThrowArgumentException_InvalidCommentValue();
             }
-            else
-            {
-                WriteCommentByOptions(utf8Value);
-            }
-        }
 
-        private void WriteCommentEscape(ReadOnlySpan<byte> utf8Value)
-        {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
-
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
-
-            if (valueIdx != -1)
-            {
-                WriteCommentEscapeValue(utf8Value, valueIdx);
-            }
-            else
-            {
-                WriteCommentByOptions(utf8Value);
-            }
+            WriteCommentByOptions(utf8Value);
         }
 
         private void WriteCommentByOptions(ReadOnlySpan<byte> utf8Value)
         {
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteCommentIndented(utf8Value);
             }
@@ -189,127 +159,62 @@ namespace System.Text.Json
             }
         }
 
-        private void WriteCommentMinimized(ReadOnlySpan<byte> escapedValue)
-        {
-            int idx = 0;
-
-            WriteCommentValue(escapedValue, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteCommentIndented(ReadOnlySpan<byte> escapedValue)
-        {
-            int idx = 0;
-            WriteFormattingPreamble(ref idx);
-
-            WriteCommentValue(escapedValue, ref idx);
-
-            Advance(idx);
-        }
-
-        private void WriteCommentEscapeValue(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal)
+        private void WriteCommentMinimized(ReadOnlySpan<byte> utf8Value)
         {
-            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
-            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
+            Debug.Assert(utf8Value.Length < int.MaxValue - 4);
 
-            byte[] valueArray = null;
+            int maxRequired = utf8Value.Length + 4; // /*...*/
 
-            int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
-
-            Span<byte> escapedValue;
-            if (length > StackallocThreshold)
-            {
-                valueArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedValue = valueArray;
-            }
-            else
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedValue = new Span<byte>(ptr, length);
-                }
+                Grow(maxRequired);
             }
-            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
 
-            WriteCommentByOptions(escapedValue.Slice(0, written));
+            Span<byte> output = _memory.Span;
 
-            if (valueArray != null)
-            {
-                ArrayPool<byte>.Shared.Return(valueArray);
-            }
+            output[BytesPending++] = JsonConstants.Slash;
+            output[BytesPending++] = JsonConstants.Asterisk;
+
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
+
+            output[BytesPending++] = JsonConstants.Asterisk;
+            output[BytesPending++] = JsonConstants.Slash;
         }
 
-        private void WriteCommentValue(ReadOnlySpan<char> escapedValue, ref int idx)
+        private void WriteCommentIndented(ReadOnlySpan<byte> utf8Value)
         {
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Slash;
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Asterisk;
+            Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - s_newLineLength);
 
-            ReadOnlySpan<byte> byteSpan = MemoryMarshal.AsBytes(escapedValue);
-            int partialConsumed = 0;
-            while (true)
-            {
-                OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written);
-                idx += written;
-                if (status == OperationStatus.Done)
-                {
-                    break;
-                }
-                partialConsumed += consumed;
-                AdvanceAndGrow(ref idx);
-            }
+            int minRequired = indent + utf8Value.Length + 4; // /*...*/
+            int maxRequired = minRequired + s_newLineLength; // Optionally, 1-2 bytes for new line
 
-            if (_buffer.Length <= idx)
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                AdvanceAndGrow(ref idx);
+                Grow(maxRequired);
             }
-            _buffer[idx++] = JsonConstants.Asterisk;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Slash;
-        }
+            Span<byte> output = _memory.Span;
 
-        private void WriteCommentValue(ReadOnlySpan<byte> escapedValue, ref int idx)
-        {
-            if (_buffer.Length <= idx)
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx);
+                WriteNewLine(output);
             }
-            _buffer[idx++] = JsonConstants.Slash;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Asterisk;
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            CopyLoop(escapedValue, ref idx);
+            output[BytesPending++] = JsonConstants.Slash;
+            output[BytesPending++] = JsonConstants.Asterisk;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Asterisk;
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
 
-            if (_buffer.Length <= idx)
-            {
-                AdvanceAndGrow(ref idx);
-            }
-            _buffer[idx++] = JsonConstants.Slash;
+            output[BytesPending++] = JsonConstants.Asterisk;
+            output[BytesPending++] = JsonConstants.Slash;
         }
     }
 }
index d68d461..b62679a 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="DateTime"/> value (as a JSON string) as an element of a JSON array.
@@ -21,7 +23,7 @@ namespace System.Text.Json
         public void WriteStringValue(DateTime value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringValueIndented(value);
             }
@@ -36,21 +38,72 @@ namespace System.Text.Json
 
         private void WriteStringValueMinimized(DateTime value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator
 
-            WriteStringValue(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringValueIndented(DateTime value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line
+            int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            WriteStringValue(value, ref idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-            Advance(idx);
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
+
+        private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O');
     }
 }
index 3edbf64..45d9aeb 100644 (file)
@@ -3,10 +3,13 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="DateTimeOffset"/> value (as a JSON string) as an element of a JSON array.
@@ -21,7 +24,7 @@ namespace System.Text.Json
         public void WriteStringValue(DateTimeOffset value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringValueIndented(value);
             }
@@ -36,21 +39,70 @@ namespace System.Text.Json
 
         private void WriteStringValueMinimized(DateTimeOffset value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            WriteStringValue(value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            output[BytesPending++] = JsonConstants.Quote;
+
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringValueIndented(DateTimeOffset value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line
+            int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteStringValue(value, ref idx);
+            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
+            bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat);
+            Debug.Assert(result);
+            JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
+            tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending));
+            BytesPending += bytesWritten;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
         }
     }
 }
index 305cf85..2ad2b1d 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="decimal"/> value (as a JSON number) as an element of a JSON array.
@@ -21,7 +23,7 @@ namespace System.Text.Json
         public void WriteNumberValue(decimal value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
@@ -36,21 +38,55 @@ namespace System.Text.Json
 
         private void WriteNumberValueMinimized(decimal value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatDecimalLength + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberValueIndented(decimal value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index 54fc046..74315e7 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="double"/> value (as a JSON number) as an element of a JSON array.
@@ -23,7 +25,7 @@ namespace System.Text.Json
             JsonWriterHelper.ValidateDouble(value);
 
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
@@ -38,21 +40,55 @@ namespace System.Text.Json
 
         private void WriteNumberValueMinimized(double value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatDoubleLength + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberValueIndented(double value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index ad8dd12..47aca25 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="float"/> value (as a JSON number) as an element of a JSON array.
@@ -23,7 +25,7 @@ namespace System.Text.Json
             JsonWriterHelper.ValidateSingle(value);
 
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
@@ -38,21 +40,55 @@ namespace System.Text.Json
 
         private void WriteNumberValueMinimized(float value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatSingleLength + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberValueIndented(float value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index a0f17ef..f5c2458 100644 (file)
@@ -3,10 +3,11 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the value (as a JSON number) as an element of a JSON array.
@@ -23,10 +24,11 @@ namespace System.Text.Json
         /// </remarks>
         internal void WriteNumberValue(ReadOnlySpan<byte> utf8FormattedNumber)
         {
+            JsonWriterHelper.ValidateValue(utf8FormattedNumber);
             JsonWriterHelper.ValidateNumber(utf8FormattedNumber);
-
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(utf8FormattedNumber);
             }
@@ -39,23 +41,57 @@ namespace System.Text.Json
             _tokenType = JsonTokenType.Number;
         }
 
-        private void WriteNumberValueMinimized(ReadOnlySpan<byte> value)
+        private void WriteNumberValueMinimized(ReadOnlySpan<byte> utf8Value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
         }
 
-        private void WriteNumberValueIndented(ReadOnlySpan<byte> value)
+        private void WriteNumberValueIndented(ReadOnlySpan<byte> utf8Value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - s_newLineLength);
+
+            int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
         }
     }
 }
index a6d17ce..59b87f1 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="Guid"/> value (as a JSON string) as an element of a JSON array.
@@ -21,7 +23,7 @@ namespace System.Text.Json
         public void WriteStringValue(Guid value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringValueIndented(value);
             }
@@ -36,21 +38,64 @@ namespace System.Text.Json
 
         private void WriteStringValueMinimized(Guid value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatGuidLength + 3; // 2 quotes, and optionally, 1 list separator
 
-            WriteStringValue(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringValueIndented(Guid value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line
+            int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
+
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = JsonConstants.Quote;
 
-            WriteStringValue(value, ref idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
         }
     }
 }
index bdbd8f8..cc4986e 100644 (file)
@@ -6,11 +6,11 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         private void ValidateWritingValue()
         {
-            if (!_writerOptions.SkipValidation)
+            if (!Options.SkipValidation)
             {
                 if (_inObject)
                 {
@@ -26,44 +26,5 @@ namespace System.Text.Json
                 }
             }
         }
-
-        private int WriteCommaAndFormattingPreamble()
-        {
-            int idx = 0;
-            WriteListSeparator(ref idx);
-            WriteFormattingPreamble(ref idx);
-            return idx;
-        }
-
-        private void WriteFormattingPreamble(ref int idx)
-        {
-            if (_tokenType != JsonTokenType.None)
-                WriteNewLine(ref idx);
-
-            int indent = Indentation;
-            while (true)
-            {
-                bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                idx += bytesWritten;
-                if (result)
-                {
-                    break;
-                }
-                indent -= bytesWritten;
-                AdvanceAndGrow(ref idx);
-            }
-        }
-
-        private void WriteListSeparator(ref int idx)
-        {
-            if (_currentDepth < 0)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
-            }
-        }
     }
 }
index 03d57f2..2a7abf4 100644 (file)
@@ -2,9 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
+
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the JSON literal "null" as an element of a JSON array.
@@ -46,7 +48,7 @@ namespace System.Text.Json
         private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteLiteralIndented(utf8Value);
             }
@@ -58,21 +60,56 @@ namespace System.Text.Json
 
         private void WriteLiteralMinimized(ReadOnlySpan<byte> utf8Value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            Debug.Assert(utf8Value.Length <= 5);
+
+            int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            CopyLoop(utf8Value, ref idx);
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
         }
 
         private void WriteLiteralIndented(ReadOnlySpan<byte> utf8Value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+            Debug.Assert(utf8Value.Length <= 5);
+
+            int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            CopyLoop(utf8Value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            utf8Value.CopyTo(output.Slice(BytesPending));
+            BytesPending += utf8Value.Length;
         }
     }
 }
index d32224d..5f772ea 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="int"/> value (as a JSON number) as an element of a JSON array.
@@ -34,7 +36,7 @@ namespace System.Text.Json
         public void WriteNumberValue(long value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
@@ -49,21 +51,55 @@ namespace System.Text.Json
 
         private void WriteNumberValueMinimized(long value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatInt64Length + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberValueIndented(long value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index e9513bb..c3422b1 100644 (file)
@@ -7,45 +7,42 @@ using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the string text value (as a JSON string) as an element of a JSON array.
         /// </summary>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStringValue(string value, bool escape = true)
-           => WriteStringValue(value.AsSpan(), escape);
+        public void WriteStringValue(string value)
+           => WriteStringValue(value.AsSpan());
 
         /// <summary>
         /// Writes the UTF-16 text value (as a JSON string) as an element of a JSON array.
         /// </summary>
         /// <param name="value">The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStringValue(ReadOnlySpan<char> value, bool escape = true)
+        public void WriteStringValue(ReadOnlySpan<char> value)
         {
             JsonWriterHelper.ValidateValue(value);
 
-            if (escape)
-            {
-                WriteStringEscape(value);
-            }
-            else
-            {
-                WriteStringByOptions(value);
-            }
+            WriteStringEscape(value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -55,7 +52,7 @@ namespace System.Text.Json
         {
             int valueIdx = JsonWriterHelper.NeedsEscaping(value);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < value.Length);
 
             if (valueIdx != -1)
             {
@@ -70,7 +67,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(value);
             }
@@ -80,23 +77,70 @@ namespace System.Text.Json
             }
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<char> escapedValue)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 3);
+
+            // All ASCII, 2 quotes => escapedValue.Length + 2
+            // Optionally, 1 list separator, and up to 3x growth when transcoding
+            int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3;
 
-            WriteStringValue(escapedValue, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            Advance(idx);
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringIndented(ReadOnlySpan<char> escapedValue)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - s_newLineLength);
+
+            // All ASCII, 2 quotes => indent + escapedValue.Length + 2
+            // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
+            int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3 + s_newLineLength;
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteStringValue(escapedValue, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            TranscodeAndWrite(escapedValue, output);
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal)
@@ -108,21 +152,10 @@ namespace System.Text.Json
 
             int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal);
 
-            Span<char> escapedValue;
-            if (length > StackallocThreshold)
-            {
-                valueArray = ArrayPool<char>.Shared.Rent(length);
-                escapedValue = valueArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedValue = new Span<char>(ptr, length);
-                }
-            }
+            Span<char> escapedValue = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (valueArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
 
             WriteStringByOptions(escapedValue.Slice(0, written));
@@ -137,25 +170,20 @@ namespace System.Text.Json
         /// Writes the UTF-8 text value (as a JSON string) as an element of a JSON array.
         /// </summary>
         /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON string element of a JSON array.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the value is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The value is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified value is too large.
         /// </exception>
         /// <exception cref="InvalidOperationException">
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStringValue(ReadOnlySpan<byte> utf8Value, bool escape = true)
+        public void WriteStringValue(ReadOnlySpan<byte> utf8Value)
         {
             JsonWriterHelper.ValidateValue(utf8Value);
 
-            if (escape)
-            {
-                WriteStringEscape(utf8Value);
-            }
-            else
-            {
-                WriteStringByOptions(utf8Value);
-            }
+            WriteStringEscape(utf8Value);
 
             SetFlagToAddListSeparatorBeforeNextItem();
             _tokenType = JsonTokenType.String;
@@ -165,7 +193,7 @@ namespace System.Text.Json
         {
             int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
 
-            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
+            Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length);
 
             if (valueIdx != -1)
             {
@@ -180,7 +208,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteStringIndented(utf8Value);
             }
@@ -190,23 +218,70 @@ namespace System.Text.Json
             }
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringMinimized(ReadOnlySpan<byte> escapedValue)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            Debug.Assert(escapedValue.Length < int.MaxValue - 3);
+
+            int minRequired = escapedValue.Length + 2; // 2 quotes
+            int maxRequired = minRequired + 1; // Optionally, 1 list separator
 
-            WriteStringValue(escapedValue, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
 
-            Advance(idx);
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
+        // TODO: https://github.com/dotnet/corefx/issues/36958
         private void WriteStringIndented(ReadOnlySpan<byte> escapedValue)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - s_newLineLength);
+
+            int minRequired = indent + escapedValue.Length + 2; // 2 quotes
+            int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteStringValue(escapedValue, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            output[BytesPending++] = JsonConstants.Quote;
+
+            escapedValue.CopyTo(output.Slice(BytesPending));
+            BytesPending += escapedValue.Length;
+
+            output[BytesPending++] = JsonConstants.Quote;
         }
 
         private void WriteStringEscapeValue(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal)
@@ -218,21 +293,10 @@ namespace System.Text.Json
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
 
-            Span<byte> escapedValue;
-            if (length > StackallocThreshold)
-            {
-                valueArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedValue = valueArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedValue = new Span<byte>(ptr, length);
-                }
-            }
+            Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (valueArray = ArrayPool<byte>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
 
             WriteStringByOptions(escapedValue.Slice(0, written));
index 107bc0a..7659709 100644 (file)
@@ -3,10 +3,12 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter
     {
         /// <summary>
         /// Writes the <see cref="uint"/> value (as a JSON number) as an element of a JSON array.
@@ -36,7 +38,7 @@ namespace System.Text.Json
         public void WriteNumberValue(ulong value)
         {
             ValidateWritingValue();
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
@@ -51,21 +53,55 @@ namespace System.Text.Json
 
         private void WriteNumberValueMinimized(ulong value)
         {
-            int idx = 0;
-            WriteListSeparator(ref idx);
+            int maxRequired = JsonConstants.MaximumFormatUInt64Length + 1; // Optionally, 1 list separator
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
 
-            Advance(idx);
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
 
         private void WriteNumberValueIndented(ulong value)
         {
-            int idx = WriteCommaAndFormattingPreamble();
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
+            {
+                Grow(maxRequired);
+            }
+
+            Span<byte> output = _memory.Span;
+
+            if (_currentDepth < 0)
+            {
+                output[BytesPending++] = JsonConstants.ListSeparator;
+            }
+
+            if (_tokenType != JsonTokenType.None)
+            {
+                WriteNewLine(output);
+            }
 
-            WriteNumberValueFormatLoop(value, ref idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
 
-            Advance(idx);
+            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            Debug.Assert(result);
+            BytesPending += bytesWritten;
         }
     }
 }
index a328f25..cdb4a99 100644 (file)
@@ -4,57 +4,47 @@
 
 using System.Buffers;
 using System.Diagnostics;
+using System.IO;
 using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace System.Text.Json
 {
     /// <summary>
     /// Provides a high-performance API for forward-only, non-cached writing of UTF-8 encoded JSON text.
+    /// </summary>
+    /// <remarks>
     /// It writes the text sequentially with no caching and adheres to the JSON RFC
     /// by default (https://tools.ietf.org/html/rfc8259), with the exception of writing comments.
-    /// </summary>
+    /// </remarks>
     /// <remarks>
     /// When the user attempts to write invalid JSON and validation is enabled, it throws
-    /// a <see cref="InvalidOperationException"/> with a context specific error message.
-    /// Since this type is a ref struct, it does not directly support async. However, it does provide
-    /// support for reentrancy to write partial data, and continue writing in chunks.
+    /// an <see cref="InvalidOperationException"/> with a context specific error message.
+    /// </remarks>
+    /// <remarks>
     /// To be able to format the output with indentation and whitespace OR to skip validation, create an instance of 
-    /// <see cref="JsonWriterState"/> and pass that in to the writer.
+    /// <see cref="JsonWriterOptions"/> and pass that in to the writer.
     /// </remarks>
-    public ref partial struct Utf8JsonWriter
+    public sealed partial class Utf8JsonWriter : IDisposable
+#if BUILDING_INBOX_LIBRARY
+        , IAsyncDisposable
+#endif
     {
-        private const int StackallocThreshold = 256;
-        private const int DefaultGrowthSize = 4096;
+        // Depending on OS, either '\r\n' OR '\n'
+        private static readonly int s_newLineLength = Environment.NewLine.Length;
 
-        private readonly IBufferWriter<byte> _output;
-        private int _buffered;
-        private Span<byte> _buffer;
+        private const int DefaultGrowthSize = 4096;
 
-        /// <summary>
-        /// Returns the total amount of bytes written by the <see cref="Utf8JsonWriter"/> so far
-        /// for the current instance of the <see cref="Utf8JsonWriter"/>.
-        /// This includes data that has been written beyond what has already been committed.
-        /// </summary>
-        public long BytesWritten
-        {
-            get
-            {
-                Debug.Assert(BytesCommitted <= long.MaxValue - _buffered);
-                return BytesCommitted + _buffered;
-            }
-        }
+        private IBufferWriter<byte> _output;
+        private Stream _stream;
+        private ArrayBufferWriter<byte> _arrayBufferWriter;
 
-        /// <summary>
-        /// Returns the total amount of bytes committed to the output by the <see cref="Utf8JsonWriter"/> so far
-        /// for the current instance of the <see cref="Utf8JsonWriter"/>.
-        /// This is how much the IBufferWriter has advanced.
-        /// </summary>
-        public long BytesCommitted { get; private set; }
+        private Memory<byte> _memory;
 
         private bool _inObject;
         private bool _isNotPrimitive;
         private JsonTokenType _tokenType;
-        private readonly JsonWriterOptions _writerOptions;
         private BitStack _bitStack;
 
         // The highest order bit of _currentDepth is used to discern whether we are writing the first item in a list or not.
@@ -62,6 +52,28 @@ namespace System.Text.Json
         // else, no list separator is needed since we are writing the first item.
         private int _currentDepth;
 
+        /// <summary>
+        /// Returns the amount of bytes written by the <see cref="Utf8JsonWriter"/> so far
+        /// that have not yet been flushed to the output and committed.
+        /// </summary>
+        public int BytesPending { get; private set; }
+
+        /// <summary>
+        /// Returns the amount of bytes committed to the output by the <see cref="Utf8JsonWriter"/> so far.
+        /// </summary>
+        /// <remarks>
+        /// In the case of IBufferwriter, this is how much the IBufferWriter has advanced.
+        /// In the case of Stream, this is how much data has been written to the stream.
+        /// </remarks>
+        public long BytesCommitted { get; private set; }
+
+        /// <summary>
+        /// Gets the custom behavior when writing JSON using
+        /// the <see cref="Utf8JsonWriter"/> which indicates whether to format the output
+        /// while writing and whether to skip structural JSON validation or not.
+        /// </summary>
+        public JsonWriterOptions Options { get; }
+
         private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent;
 
         /// <summary>
@@ -71,99 +83,308 @@ namespace System.Text.Json
         public int CurrentDepth => _currentDepth & JsonConstants.RemoveFlagsBitMask;
 
         /// <summary>
-        /// Returns the current snapshot of the <see cref="Utf8JsonWriter"/> state which must
-        /// be captured by the caller and passed back in to the <see cref="Utf8JsonWriter"/> ctor with more data.
+        /// Constructs a new <see cref="Utf8JsonWriter"/> instance with a specified <paramref name="bufferWriter"/>.
         /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown when there is JSON data that has been written and buffered but not yet flushed to the <see cref="IBufferWriter{Byte}" />.   
-        /// Getting the state for creating a new <see cref="Utf8JsonWriter"/> without first committing the data that has been written  
-        /// would result in an inconsistent state. Call Flush before getting the current state.        
+        /// <param name="bufferWriter">An instance of <see cref="IBufferWriter{Byte}" /> used as a destination for writing JSON text into.</param>
+        /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonWriter"/>
+        /// By default, the <see cref="Utf8JsonWriter"/> writes JSON minimized (i.e. with no extra whitespace)
+        /// and validates that the JSON being written is structurally valid according to JSON RFC.</param>
+        /// <exception cref="ArgumentNullException">
+        /// Thrown when the instance of <see cref="IBufferWriter{Byte}" /> that is passed in is null.
+        /// </exception>
+        public Utf8JsonWriter(IBufferWriter<byte> bufferWriter, JsonWriterOptions options = default)
+        {
+            _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter));
+            _stream = default;
+            _arrayBufferWriter = default;
+
+            BytesPending = default;
+            BytesCommitted = default;
+            _memory = default;
+
+            _inObject = default;
+            _isNotPrimitive = default;
+            _tokenType = default;
+            _currentDepth = default;
+            Options = options;
+
+            // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
+            // This way we avoid allocations in the common, default cases, and allocate lazily.
+            _bitStack = default;
+        }
+
+        /// <summary>
+        /// Constructs a new <see cref="Utf8JsonWriter"/> instance with a specified <paramref name="utf8Json"/>.
+        /// </summary>
+        /// <param name="utf8Json">An instance of <see cref="Stream" /> used as a destination for writing JSON text into.</param>
+        /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonWriter"/>
+        /// By default, the <see cref="Utf8JsonWriter"/> writes JSON minimized (i.e. with no extra whitespace)
+        /// and validates that the JSON being written is structurally valid according to JSON RFC.</param>
+        /// <exception cref="ArgumentNullException">
+        /// Thrown when the instance of <see cref="Stream" /> that is passed in is null.
         /// </exception>
+        public Utf8JsonWriter(Stream utf8Json, JsonWriterOptions options = default)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+            if (!utf8Json.CanWrite)
+                throw new ArgumentException(SR.StreamNotWritable);
+
+            _stream = utf8Json;
+            _arrayBufferWriter = new ArrayBufferWriter<byte>();
+            _output = _arrayBufferWriter;
+
+            BytesPending = default;
+            BytesCommitted = default;
+            _memory = default;
+
+            _inObject = default;
+            _isNotPrimitive = default;
+            _tokenType = default;
+            _currentDepth = default;
+            Options = options;
+
+            // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
+            // This way we avoid allocations in the common, default cases, and allocate lazily.
+            _bitStack = default;
+        }
+
+        /// <summary>
+        /// Resets the <see cref="Utf8JsonWriter"/> internal state so that it can be re-used.
+        /// </summary>
         /// <remarks>
-        /// Unlike the <see cref="Utf8JsonWriter"/>, which is a ref struct, the state can survive
-        /// across async/await boundaries and hence this type is required to provide support for reading
-        /// in more data asynchronously before continuing with a new instance of the <see cref="Utf8JsonWriter"/>.
+        /// The <see cref="Utf8JsonWriter"/> will continue to use the original writer options
+        /// and the original output as the destination (either <see cref="IBufferWriter{Byte}" /> or <see cref="Stream" />).
         /// </remarks>
-        public JsonWriterState GetCurrentState()
+        public void Reset()
         {
-            if (_buffered != 0)
+            if (_arrayBufferWriter != null)
             {
-                throw ThrowHelper.GetInvalidOperationException_CallFlushFirst(_buffered);
+                _arrayBufferWriter.Clear();
             }
-            return new JsonWriterState
+            ResetHelper();
+        }
+
+        /// <summary>
+        /// Resets the <see cref="Utf8JsonWriter"/> internal state so that it can be re-used with the new instance of <see cref="Stream" />.
+        /// </summary>
+        /// <param name="utf8Json">An instance of <see cref="Stream" /> used as a destination for writing JSON text into.</param>
+        /// <remarks>
+        /// The <see cref="Utf8JsonWriter"/> will continue to use the original writer options
+        /// but now write to the passed in <see cref="Stream" /> as the new destination.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException">
+        /// Thrown when the instance of <see cref="Stream" /> that is passed in is null.
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">
+        ///   The instance of <see cref="Utf8JsonWriter"/> has been disposed.
+        /// </exception>
+        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)
+            {
+                _arrayBufferWriter = new ArrayBufferWriter<byte>();
+            }
+            else
             {
-                _bytesWritten = BytesWritten,
-                _bytesCommitted = BytesCommitted,
-                _inObject = _inObject,
-                _isNotPrimitive = _isNotPrimitive,
-                _tokenType = _tokenType,
-                _currentDepth = _currentDepth,
-                _writerOptions = _writerOptions,
-                _bitStack = _bitStack,
-            };
+                _arrayBufferWriter.Clear();
+            }
+            _output = _arrayBufferWriter;
+
+            ResetHelper();
         }
 
         /// <summary>
-        /// Constructs a new <see cref="Utf8JsonWriter"/> instance with a specified <paramref name="bufferWriter"/>.
+        /// Resets the <see cref="Utf8JsonWriter"/> internal state so that it can be re-used with the new instance of <see cref="IBufferWriter{Byte}" />.
         /// </summary>
         /// <param name="bufferWriter">An instance of <see cref="IBufferWriter{Byte}" /> used as a destination for writing JSON text into.</param>
-        /// <param name="state">If this is the first call to the ctor, pass in a default state. Otherwise,
-        /// capture the state from the previous instance of the <see cref="Utf8JsonWriter"/> and pass that back.</param>
+        /// <remarks>
+        /// The <see cref="Utf8JsonWriter"/> will continue to use the original writer options
+        /// but now write to the passed in <see cref="IBufferWriter{Byte}" /> as the new destination.
+        /// </remarks>
         /// <exception cref="ArgumentNullException">
         /// Thrown when the instance of <see cref="IBufferWriter{Byte}" /> that is passed in is null.
         /// </exception>
-        /// <remarks>
-        /// Since this type is a ref struct, it is a stack-only type and all the limitations of ref structs apply to it.
-        /// This is the reason why the ctor accepts a <see cref="JsonWriterState"/>.
-        /// </remarks>
-        public Utf8JsonWriter(IBufferWriter<byte> bufferWriter, JsonWriterState state = default)
+        /// <exception cref="ObjectDisposedException">
+        ///   The instance of <see cref="Utf8JsonWriter"/> has been disposed.
+        /// </exception>
+        public void Reset(IBufferWriter<byte> bufferWriter)
         {
+            CheckNotDisposed();
+
             _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter));
-            _buffered = 0;
-            BytesCommitted = 0;
-            _buffer = _output.GetSpan();
+            _stream = default;
+
+            if (_arrayBufferWriter != null)
+            {
+                _arrayBufferWriter.Clear();
+            }
 
-            _inObject = state._inObject;
-            _isNotPrimitive = state._isNotPrimitive;
-            _tokenType = state._tokenType;
-            _writerOptions = state._writerOptions;
-            _bitStack = state._bitStack;
+            _arrayBufferWriter = default;
 
-            _currentDepth = state._currentDepth;
+            ResetHelper();
         }
 
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void Advance(int count)
+        private void ResetHelper()
         {
-            Debug.Assert(count >= 0 && _buffered <= int.MaxValue - count);
+            BytesPending = default;
+            BytesCommitted = default;
+            _memory = default;
+
+            _inObject = default;
+            _isNotPrimitive = default;
+            _tokenType = default;
+            _currentDepth = default;
+
+            // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
+            // This way we avoid allocations in the common, default cases, and allocate lazily.
+            _bitStack = default;
+        }
 
-            _buffered += count;
-            _buffer = _buffer.Slice(count);
+        private void CheckNotDisposed()
+        {
+            if (_output == null)
+            {
+                throw new ObjectDisposedException(nameof(Utf8JsonWriter));
+            }
         }
 
         /// <summary>
-        /// Advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
+        /// Commits the JSON text written so far which makes it visible to the output destination.
         /// </summary>
-        /// <param name="isFinalBlock">Let's the writer know whether more data will be written. This is used to validate
-        /// that the JSON written so far is structurally valid if no more data is to follow.</param>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown when incomplete JSON has been written and <paramref name="isFinalBlock"/> is true.
-        /// (for example when an open object or array needs to be closed).
-        /// </exception>
-        public void Flush(bool isFinalBlock = true)
+        /// <remarks>
+        /// In the case of IBufferWriter, this advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
+        /// In the case of Stream, this writes the data to the stream and flushes it.
+        /// </remarks>
+        public void Flush()
         {
-            if (isFinalBlock && !_writerOptions.SkipValidation && (CurrentDepth != 0 || _tokenType == JsonTokenType.None))
-                ThrowHelper.ThrowInvalidOperationException_DepthNonZeroOrEmptyJson(_currentDepth);
+            FlushHelper();
+
+            if (_stream != null)
+            {
+                FlushHelperStream();
+                _stream.Flush();
+            }
+
+            _memory = default;
+            BytesCommitted += BytesPending;
+            BytesPending = 0;
+        }
+
+        /// <summary>
+        /// Commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance.
+        /// </summary>
+        /// <remarks>
+        /// In the case of IBufferWriter, this advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
+        /// In the case of Stream, this writes the data to the stream and flushes it.
+        /// </remarks>
+        /// <remarks>
+        /// The <see cref="Utf8JsonWriter"/> instance cannot be re-used after disposing.
+        /// </remarks>
+        public void Dispose()
+        {
+            if (_output == null)
+            {
+                return;
+            }
 
             Flush();
+            ResetHelper();
+
+            _stream = null;
+            _arrayBufferWriter = null;
+            _output = null;
+        }
+
+#if BUILDING_INBOX_LIBRARY
+        /// <summary>
+        /// Asynchronously commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance.
+        /// </summary>
+        /// <remarks>
+        /// In the case of IBufferWriter, this advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
+        /// In the case of Stream, this writes the data to the stream and flushes it.
+        /// </remarks>
+        /// <remarks>
+        /// The <see cref="Utf8JsonWriter"/> instance cannot be re-used after disposing.
+        /// </remarks>
+        public async ValueTask DisposeAsync()
+        {
+            if (_output == null)
+            {
+                return;
+            }
+
+            await FlushAsync().ConfigureAwait(false);
+            ResetHelper();
+
+            _stream = null;
+            _arrayBufferWriter = null;
+            _output = null;
+        }
+#endif
+
+        /// <summary>
+        /// Asynchronously commits the JSON text written so far which makes it visible to the output destination.
+        /// </summary>
+        /// <remarks>
+        /// In the case of IBufferWriter, this advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
+        /// In the case of Stream, this writes the data to the stream and flushes it asynchronously, while monitoring cancellation requests.
+        /// </remarks>
+        public async Task FlushAsync(CancellationToken cancellationToken = default)
+        {
+            FlushHelper();
+
+            if (_stream != null)
+            {
+                Debug.Assert(_arrayBufferWriter != null);
+                Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount);
+                if (BytesPending != 0)
+                {
+#if BUILDING_INBOX_LIBRARY
+                    await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false);
+#else
+                    Debug.Assert(_arrayBufferWriter.WrittenMemory.Length == _arrayBufferWriter.WrittenCount);
+                    await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory.ToArray(), 0, _arrayBufferWriter.WrittenCount, cancellationToken).ConfigureAwait(false);
+#endif
+                    _arrayBufferWriter.Clear();
+                }
+                await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            _memory = default;
+            BytesCommitted += BytesPending;
+            BytesPending = 0;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void FlushHelperStream()
+        {
+            Debug.Assert(_arrayBufferWriter != null);
+            Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount);
+            if (BytesPending != 0)
+            {
+#if BUILDING_INBOX_LIBRARY
+                _stream.Write(_arrayBufferWriter.WrittenSpan);
+#else
+                Debug.Assert(_arrayBufferWriter.WrittenSpan.Length == _arrayBufferWriter.WrittenCount);
+                _stream.Write(_arrayBufferWriter.WrittenSpan.ToArray(), 0, _arrayBufferWriter.WrittenCount);
+#endif
+                _arrayBufferWriter.Clear();
+            }
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void Flush()
+        private void FlushHelper()
         {
-            _output.Advance(_buffered);
-            BytesCommitted += _buffered;
-            _buffered = 0;
+            _output.Advance(BytesPending);
         }
 
         /// <summary>
@@ -197,7 +418,7 @@ namespace System.Text.Json
             if (CurrentDepth >= JsonConstants.MaxWriterDepth)
                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth, token: default, tokenType: default);
 
-            if (_writerOptions.IndentedOrNotSkipValidation)
+            if (Options.IndentedOrNotSkipValidation)
             {
                 WriteStartSlow(token);
             }
@@ -213,32 +434,26 @@ namespace System.Text.Json
 
         private void WriteStartMinimized(byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
+            if (_memory.Length - BytesPending < 2)  // 1 start token, and optionally, 1 list separator
             {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
+                Grow(2);
             }
 
-            if (_buffer.Length <= idx)
+            Span<byte> output = _memory.Span;
+            if (_currentDepth < 0)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
-            _buffer[idx++] = token;
-
-            Advance(idx);
+            output[BytesPending++] = token;
         }
 
         private void WriteStartSlow(byte token)
         {
-            Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation);
+            Debug.Assert(Options.Indented || !Options.SkipValidation);
 
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
-                if (!_writerOptions.SkipValidation)
+                if (!Options.SkipValidation)
                 {
                     ValidateStart();
                     UpdateBitStackOnStart(token);
@@ -247,7 +462,7 @@ namespace System.Text.Json
             }
             else
             {
-                Debug.Assert(!_writerOptions.SkipValidation);
+                Debug.Assert(!Options.SkipValidation);
                 ValidateStart();
                 UpdateBitStackOnStart(token);
                 WriteStartMinimized(token);
@@ -273,46 +488,42 @@ namespace System.Text.Json
 
         private void WriteStartIndented(byte token)
         {
-            int idx = 0;
-            if (_currentDepth < 0)
+            int indent = Indentation;
+            Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+            int minRequired = indent + 1;   // 1 start token
+            int maxRequired = minRequired + 3; // Optionally, 1 list separator and 1-2 bytes for new line
+
+            if (_memory.Length - BytesPending < maxRequired)
             {
-                if (_buffer.Length <= idx)
-                {
-                    GrowAndEnsure();
-                }
-                _buffer[idx++] = JsonConstants.ListSeparator;
+                Grow(maxRequired);
             }
 
-            if (_tokenType != JsonTokenType.None)
-                WriteNewLine(ref idx);
+            Span<byte> output = _memory.Span;
 
-            int indent = Indentation;
-            while (true)
+            if (_currentDepth < 0)
             {
-                bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                idx += bytesWritten;
-                if (result)
-                {
-                    break;
-                }
-                indent -= bytesWritten;
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            if (_buffer.Length <= idx)
+            if (_tokenType != JsonTokenType.None)
             {
-                AdvanceAndGrow(ref idx);
+                WriteNewLine(output);
             }
-            _buffer[idx++] = token;
 
-            Advance(idx);
+            JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+            BytesPending += indent;
+
+            output[BytesPending++] = token;
         }
 
         /// <summary>
         /// Writes the beginning of a JSON array with a property name as the key.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON array to be written.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -320,18 +531,11 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartArray(ReadOnlySpan<byte> utf8PropertyName, bool escape = true)
+        public void WriteStartArray(ReadOnlySpan<byte> utf8PropertyName)
         {
             ValidatePropertyNameAndDepth(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket);
-            }
-            else
-            {
-                WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBracket);
-            }
+            WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket);
 
             _currentDepth &= JsonConstants.RemoveFlagsBitMask;
             _currentDepth++;
@@ -343,7 +547,9 @@ namespace System.Text.Json
         /// Writes the beginning of a JSON object with a property name as the key.
         /// </summary>
         /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -351,18 +557,11 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartObject(ReadOnlySpan<byte> utf8PropertyName, bool escape = true)
+        public void WriteStartObject(ReadOnlySpan<byte> utf8PropertyName)
         {
             ValidatePropertyNameAndDepth(utf8PropertyName);
 
-            if (escape)
-            {
-                WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace);
-            }
-            else
-            {
-                WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBrace);
-            }
+            WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace);
 
             _currentDepth &= JsonConstants.RemoveFlagsBitMask;
             _currentDepth++;
@@ -374,7 +573,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -389,24 +588,15 @@ namespace System.Text.Json
         private void WriteStartByOptions(ReadOnlySpan<byte> utf8PropertyName, byte token)
         {
             ValidateWritingProperty(token);
-            int idx;
-            if (_writerOptions.Indented)
+
+            if (Options.Indented)
             {
-                idx = WritePropertyNameIndented(utf8PropertyName);
+                WritePropertyNameIndented(utf8PropertyName, token);
             }
             else
             {
-                idx = WritePropertyNameMinimized(utf8PropertyName);
-            }
-
-            if (1 > _buffer.Length - idx)
-            {
-                AdvanceAndGrow(ref idx, 1);
+                WritePropertyNameMinimized(utf8PropertyName, token);
             }
-
-            _buffer[idx++] = token;
-
-            Advance(idx);
         }
 
         private void WriteStartEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, byte token, int firstEscapeIndexProp)
@@ -417,21 +607,10 @@ namespace System.Text.Json
             byte[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
-            Span<byte> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<byte>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    byte* ptr = stackalloc byte[length];
-                    escapedPropertyName = new Span<byte>(ptr, length);
-                }
-            }
+
+            Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc byte[length] :
+                (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
@@ -447,7 +626,9 @@ namespace System.Text.Json
         /// Writes the beginning of a JSON array with a property name as the key.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -455,14 +636,16 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartArray(string propertyName, bool escape = true)
-            => WriteStartArray(propertyName.AsSpan(), escape);
+        public void WriteStartArray(string propertyName)
+            => WriteStartArray(propertyName.AsSpan());
 
         /// <summary>
         /// Writes the beginning of a JSON object with a property name as the key.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -470,14 +653,16 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartObject(string propertyName, bool escape = true)
-            => WriteStartObject(propertyName.AsSpan(), escape);
+        public void WriteStartObject(string propertyName)
+            => WriteStartObject(propertyName.AsSpan());
 
         /// <summary>
         /// Writes the beginning of a JSON array with a property name as the key.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -485,18 +670,11 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartArray(ReadOnlySpan<char> propertyName, bool escape = true)
+        public void WriteStartArray(ReadOnlySpan<char> propertyName)
         {
             ValidatePropertyNameAndDepth(propertyName);
 
-            if (escape)
-            {
-                WriteStartEscape(propertyName, JsonConstants.OpenBracket);
-            }
-            else
-            {
-                WriteStartByOptions(propertyName, JsonConstants.OpenBracket);
-            }
+            WriteStartEscape(propertyName, JsonConstants.OpenBracket);
 
             _currentDepth &= JsonConstants.RemoveFlagsBitMask;
             _currentDepth++;
@@ -508,7 +686,9 @@ namespace System.Text.Json
         /// Writes the beginning of a JSON object with a property name as the key.
         /// </summary>
         /// <param name="propertyName">The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
-        /// <param name="escape">If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step.</param>
+        /// <remarks>
+        /// The property name is escaped before writing.
+        /// </remarks>
         /// <exception cref="ArgumentException">
         /// Thrown when the specified property name is too large.
         /// </exception>
@@ -516,18 +696,11 @@ namespace System.Text.Json
         /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 
         /// OR if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
-        public void WriteStartObject(ReadOnlySpan<char> propertyName, bool escape = true)
+        public void WriteStartObject(ReadOnlySpan<char> propertyName)
         {
             ValidatePropertyNameAndDepth(propertyName);
 
-            if (escape)
-            {
-                WriteStartEscape(propertyName, JsonConstants.OpenBrace);
-            }
-            else
-            {
-                WriteStartByOptions(propertyName, JsonConstants.OpenBrace);
-            }
+            WriteStartEscape(propertyName, JsonConstants.OpenBrace);
 
             _currentDepth &= JsonConstants.RemoveFlagsBitMask;
             _currentDepth++;
@@ -539,7 +712,7 @@ namespace System.Text.Json
         {
             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
 
-            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
             if (propertyIdx != -1)
             {
@@ -554,24 +727,15 @@ namespace System.Text.Json
         private void WriteStartByOptions(ReadOnlySpan<char> propertyName, byte token)
         {
             ValidateWritingProperty(token);
-            int idx;
-            if (_writerOptions.Indented)
+
+            if (Options.Indented)
             {
-                idx = WritePropertyNameIndented(propertyName);
+                WritePropertyNameIndented(propertyName, token);
             }
             else
             {
-                idx = WritePropertyNameMinimized(propertyName);
-            }
-
-            if (1 > _buffer.Length - idx)
-            {
-                AdvanceAndGrow(ref idx, 1);
+                WritePropertyNameMinimized(propertyName, token);
             }
-
-            _buffer[idx++] = token;
-
-            Advance(idx);
         }
 
         private void WriteStartEscapeProperty(ReadOnlySpan<char> propertyName, byte token, int firstEscapeIndexProp)
@@ -582,21 +746,11 @@ namespace System.Text.Json
             char[] propertyArray = null;
 
             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
-            Span<char> escapedPropertyName;
-            if (length > StackallocThreshold)
-            {
-                propertyArray = ArrayPool<char>.Shared.Rent(length);
-                escapedPropertyName = propertyArray;
-            }
-            else
-            {
-                // Cannot create a span directly since it gets passed to instance methods on a ref struct.
-                unsafe
-                {
-                    char* ptr = stackalloc char[length];
-                    escapedPropertyName = new Span<char>(ptr, length);
-                }
-            }
+
+            Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+                stackalloc char[length] :
+                (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
 
             WriteStartByOptions(escapedPropertyName.Slice(0, written), token);
@@ -633,7 +787,7 @@ namespace System.Text.Json
 
         private void WriteEnd(byte token)
         {
-            if (_writerOptions.IndentedOrNotSkipValidation)
+            if (Options.IndentedOrNotSkipValidation)
             {
                 WriteEndSlow(token);
             }
@@ -652,22 +806,22 @@ namespace System.Text.Json
 
         private void WriteEndMinimized(byte token)
         {
-            if (_buffer.Length < 1)
+            if (_memory.Length - BytesPending < 1) // 1 end token
             {
-                GrowAndEnsure();
+                Grow(1);
             }
 
-            _buffer[0] = token;
-            Advance(1);
+            Span<byte> output = _memory.Span;
+            output[BytesPending++] = token;
         }
 
         private void WriteEndSlow(byte token)
         {
-            Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation);
+            Debug.Assert(Options.Indented || !Options.SkipValidation);
 
-            if (_writerOptions.Indented)
+            if (Options.Indented)
             {
-                if (!_writerOptions.SkipValidation)
+                if (!Options.SkipValidation)
                 {
                     ValidateEnd(token);
                 }
@@ -675,7 +829,7 @@ namespace System.Text.Json
             }
             else
             {
-                Debug.Assert(!_writerOptions.SkipValidation);
+                Debug.Assert(!Options.SkipValidation);
                 ValidateEnd(token);
                 WriteEndMinimized(token);
             }
@@ -716,10 +870,8 @@ namespace System.Text.Json
             }
             else
             {
-                int idx = 0;
-                WriteNewLine(ref idx);
-
                 int indent = Indentation;
+
                 // Necessary if WriteEndX is called without a corresponding WriteStartX first.
                 if (indent != 0)
                 {
@@ -727,46 +879,37 @@ namespace System.Text.Json
                     // current depth yet, explicitly subtract here.
                     indent -= JsonConstants.SpacesPerIndent;
                 }
-                while (true)
-                {
-                    bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten);
-                    idx += bytesWritten;
-                    if (result)
-                    {
-                        break;
-                    }
-                    indent -= bytesWritten;
-                    AdvanceAndGrow(ref idx);
-                }
 
-                if (_buffer.Length <= idx)
+                Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+                Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.None);
+
+                int maxRequired = indent + 3; // 1 end token, 1-2 bytes for new line
+
+                if (_memory.Length - BytesPending < maxRequired)
                 {
-                    AdvanceAndGrow(ref idx);
+                    Grow(maxRequired);
                 }
-                _buffer[idx++] = token;
 
-                Advance(idx);
+                Span<byte> output = _memory.Span;
+
+                WriteNewLine(output);
+
+                JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+                BytesPending += indent;
+
+                output[BytesPending++] = token;
             }
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void WriteNewLine(ref int idx)
+        private void WriteNewLine(Span<byte> output)
         {
             // Write '\r\n' OR '\n', depending on OS
-            if (Environment.NewLine.Length == 2)
-            {
-                if (_buffer.Length <= idx)
-                {
-                    AdvanceAndGrow(ref idx);
-                }
-                _buffer[idx++] = JsonConstants.CarriageReturn;
-            }
-
-            if (_buffer.Length <= idx)
+            if (s_newLineLength == 2)
             {
-                AdvanceAndGrow(ref idx);
+                output[BytesPending++] = JsonConstants.CarriageReturn;
             }
-            _buffer[idx++] = JsonConstants.LineFeed;
+            output[BytesPending++] = JsonConstants.LineFeed;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -785,61 +928,43 @@ namespace System.Text.Json
             }
         }
 
-        private void GrowAndEnsure()
+        private void Grow(int requiredSize)
         {
-            Flush();
-            int previousSpanLength = _buffer.Length;
-            Debug.Assert(previousSpanLength < DefaultGrowthSize);
-            _buffer = _output.GetSpan(DefaultGrowthSize);
-            if (_buffer.Length <= previousSpanLength)
-            {
-                ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetLargerSpan);
-            }
-        }
+            Debug.Assert(requiredSize > 0);
 
-        private void GrowAndEnsure(int minimumSize)
-        {
-            Flush();
-            Debug.Assert(minimumSize < DefaultGrowthSize);
-            _buffer = _output.GetSpan(DefaultGrowthSize);
-            if (_buffer.Length < minimumSize)
+            if (_memory.Length == 0)
             {
-                ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetMinimumSizeSpan, minimumSize);
+                Debug.Assert(BytesPending == 0);
+                _memory = _output.GetMemory();
+                if (_memory.Length >= requiredSize)
+                {
+                    return;
+                }
             }
-        }
 
-        private void AdvanceAndGrow(ref int alreadyWritten)
-        {
-            Debug.Assert(alreadyWritten >= 0);
-            Advance(alreadyWritten);
-            GrowAndEnsure();
-            alreadyWritten = 0;
-        }
+            FlushHelper();
 
-        private void AdvanceAndGrow(ref int alreadyWritten, int minimumSize)
-        {
-            Debug.Assert(minimumSize >= 1 && minimumSize <= 128);
-            Advance(alreadyWritten);
-            GrowAndEnsure(minimumSize);
-            alreadyWritten = 0;
-        }
+            int sizeHint = Math.Max(DefaultGrowthSize, requiredSize);
 
-        private void CopyLoop(ReadOnlySpan<byte> span, ref int idx)
-        {
-            while (true)
+            if (_stream != null)
             {
-                if (span.Length <= _buffer.Length - idx)
+                FlushHelperStream();
+                _memory = _output.GetMemory(sizeHint);
+
+                Debug.Assert(_memory.Length >= sizeHint);
+            }
+            else
+            {
+                _memory = _output.GetMemory(sizeHint);
+
+                if (_memory.Length < sizeHint)
                 {
-                    span.CopyTo(_buffer.Slice(idx));
-                    idx += span.Length;
-                    break;
+                    ThrowHelper.ThrowInvalidOperationException_NeedLargerSpan();
                 }
-
-                span.Slice(0, _buffer.Length - idx).CopyTo(_buffer.Slice(idx));
-                span = span.Slice(_buffer.Length - idx);
-                idx = _buffer.Length;
-                AdvanceAndGrow(ref idx);
             }
+
+            BytesCommitted += BytesPending;
+            BytesPending = 0;
         }
 
         private void SetFlagToAddListSeparatorBeforeNextItem()
diff --git a/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs b/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs
deleted file mode 100644 (file)
index 13249f8..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-
-namespace System.Text.Json.Tests
-{
-    internal class ArrayBufferWriter : IBufferWriter<byte>, IDisposable
-    {
-        private ResizableArray<byte> _buffer;
-
-        public ArrayBufferWriter(int capacity)
-        {
-            _buffer = new ResizableArray<byte>(ArrayPool<byte>.Shared.Rent(capacity));
-        }
-
-        public int CommitedByteCount => _buffer.Count;
-
-        public void Clear()
-        {
-            _buffer.Count = 0;
-        }
-
-        public ArraySegment<byte> Free => _buffer.Free;
-
-        public ArraySegment<byte> Formatted => _buffer.Full;
-
-        public Memory<byte> GetMemory(int minimumLength = 0)
-        {
-            if (minimumLength < 1)
-            {
-                minimumLength = 1;
-            }
-
-            if (minimumLength > _buffer.FreeCount)
-            {
-                int doubleCount = _buffer.FreeCount * 2;
-                int newSize = minimumLength > doubleCount ? minimumLength : doubleCount;
-                byte[] newArray = ArrayPool<byte>.Shared.Rent(newSize + _buffer.Count);
-                byte[] oldArray = _buffer.Resize(newArray);
-                ArrayPool<byte>.Shared.Return(oldArray);
-            }
-
-            return _buffer.FreeMemory;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public Span<byte> GetSpan(int minimumLength = 0)
-        {
-            if (minimumLength < 1)
-            {
-                minimumLength = 1;
-            }
-
-            if (minimumLength > _buffer.FreeCount)
-            {
-                int doubleCount = _buffer.FreeCount * 2;
-                int newSize = minimumLength > doubleCount ? minimumLength : doubleCount;
-                byte[] newArray = ArrayPool<byte>.Shared.Rent(newSize + _buffer.Count);
-                byte[] oldArray = _buffer.Resize(newArray);
-                ArrayPool<byte>.Shared.Return(oldArray);
-            }
-
-            return _buffer.FreeSpan;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void Advance(int bytes)
-        {
-            _buffer.Count += bytes;
-            if (_buffer.Count > _buffer.Capacity)
-            {
-                throw new InvalidOperationException("More bytes commited than returned from FreeBuffer");
-            }
-        }
-
-        public void Dispose()
-        {
-            byte[] array = _buffer.Array;
-            _buffer.Array = null;
-            ArrayPool<byte>.Shared.Return(array);
-        }
-    }
-}
index 79e7c34..b22e796 100644 (file)
@@ -26,6 +26,8 @@ namespace System.Text.Json.Tests
 
         public byte[] Formatted => _buffer.AsSpan(0, _count).ToArray();
 
+        public int FormattedCount => _count;
+
         public Memory<byte> GetMemory(int minimumLength = 0) => _buffer.AsMemory(_count);
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -34,11 +36,11 @@ namespace System.Text.Json.Tests
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void Advance(int bytes)
         {
-            _count += bytes;
-            if (_count > _buffer.Length)
+            if (_count > _buffer.Length - bytes)
             {
                 throw new InvalidOperationException("Cannot advance past the end of the buffer.");
             }
+            _count += bytes;
         }
     }
 }
index b059d05..407f5ef 100644 (file)
@@ -1373,19 +1373,19 @@ namespace System.Text.Json.Tests
                 Assert.Throws<ObjectDisposedException>(() =>
                 {
                     Utf8JsonWriter writer = default;
-                    root.WriteAsValue(ref writer);
+                    root.WriteAsValue(writer);
                 });
 
                 Assert.Throws<ObjectDisposedException>(() =>
                 {
                     Utf8JsonWriter writer = default;
-                    root.WriteAsProperty(ReadOnlySpan<char>.Empty, ref writer);
+                    root.WriteAsProperty(ReadOnlySpan<char>.Empty, writer);
                 });
 
                 Assert.Throws<ObjectDisposedException>(() =>
                 {
                     Utf8JsonWriter writer = default;
-                    root.WriteAsProperty(ReadOnlySpan<byte>.Empty, ref writer);
+                    root.WriteAsProperty(ReadOnlySpan<byte>.Empty, writer);
                 });
             }
         }
@@ -1418,19 +1418,19 @@ namespace System.Text.Json.Tests
             Assert.Throws<InvalidOperationException>(() =>
             {
                 Utf8JsonWriter writer = default;
-                root.WriteAsValue(ref writer);
+                root.WriteAsValue(writer);
             });
 
             Assert.Throws<InvalidOperationException>(() =>
             {
                 Utf8JsonWriter writer = default;
-                root.WriteAsProperty(ReadOnlySpan<char>.Empty, ref writer);
+                root.WriteAsProperty(ReadOnlySpan<char>.Empty, writer);
             });
 
             Assert.Throws<InvalidOperationException>(() =>
             {
                 Utf8JsonWriter writer = default;
-                root.WriteAsProperty(ReadOnlySpan<byte>.Empty, ref writer);
+                root.WriteAsProperty(ReadOnlySpan<byte>.Empty, writer);
             });
         }
 
index ec0a2a3..70e2008 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using Xunit;
+using System.Buffers;
 
 namespace System.Text.Json.Tests
 {
@@ -138,7 +139,7 @@ namespace System.Text.Json.Tests
         {
             WriteSimpleValue(indented, "false");
         }
-        
+
         [Theory]
         [InlineData(false)]
         [InlineData(true)]
@@ -391,6 +392,26 @@ null,
         [Theory]
         [InlineData(false)]
         [InlineData(true)]
+        public static void WriteNumberAsPropertyWithLargeName(bool indented)
+        {
+            var charArray = new char[300];
+            charArray.AsSpan().Fill('a');
+            charArray[0] = (char)0xEA;
+            var propertyName = new string(charArray);
+
+            WritePropertyValueBothForms(
+                indented,
+                propertyName,
+                "42",
+                @"{
+  ""\u00ea" + propertyName.Substring(1) + @""": 42
+}",
+                $"{{\"\\u00ea{propertyName.Substring(1)}\":42}}");
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
         public static void WriteNumberScientificAsProperty(bool indented)
         {
             WritePropertyValueBothForms(
@@ -806,15 +827,14 @@ null,
             closeBrackets.Fill((byte)']');
             jsonIn.AsSpan(SpacesPre + TargetDepth + SpacesSplit + TargetDepth).Fill((byte)' ');
 
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(jsonIn.Length))
+            var buffer = new ArrayBufferWriter<byte>(jsonIn.Length);
             using (JsonDocument doc = JsonDocument.Parse(jsonIn, optionsCopy))
             {
                 var writer = new Utf8JsonWriter(buffer);
-                doc.RootElement.WriteAsValue(ref writer);
+                doc.RootElement.WriteAsValue(writer);
                 writer.Flush();
 
-                ArraySegment<byte> formattedSegment = buffer.Formatted;
-                ReadOnlySpan<byte> formatted = formattedSegment;
+                ReadOnlySpan<byte> formatted = buffer.WrittenSpan;
 
                 Assert.Equal(TargetDepth + TargetDepth, formatted.Length);
                 Assert.True(formatted.Slice(0, TargetDepth).SequenceEqual(openBrackets), "OpenBrackets match");
@@ -827,26 +847,25 @@ null,
         [InlineData(true)]
         public static void WritePropertyOutsideObject(bool skipValidation)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions))
             {
                 JsonElement root = doc.RootElement;
-                JsonWriterState state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        SkipValidation = skipValidation,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    SkipValidation = skipValidation,
+                };
 
                 const string CharLabel = "char";
                 byte[] byteUtf8 = Encoding.UTF8.GetBytes("byte");
-                Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
 
                 if (skipValidation)
                 {
                     foreach (JsonElement val in root.EnumerateArray())
                     {
-                        val.WriteAsProperty(CharLabel.AsSpan(), ref writer);
-                        val.WriteAsProperty(byteUtf8, ref writer);
+                        val.WriteAsProperty(CharLabel.AsSpan(), writer);
+                        val.WriteAsProperty(byteUtf8, writer);
                     }
 
                     writer.Flush();
@@ -867,18 +886,14 @@ null,
                     {
                         JsonTestHelper.AssertThrows<InvalidOperationException>(
                             ref writer,
-                            (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), ref w));
+                            (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), w));
 
                         JsonTestHelper.AssertThrows<InvalidOperationException>(
                             ref writer,
-                            (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, ref w));
+                            (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, w));
                     }
 
-                    JsonTestHelper.AssertThrows<InvalidOperationException>(
-                        ref writer,
-                        (ref Utf8JsonWriter w) => w.Flush());
-
-                    writer.Flush(isFinalBlock: false);
+                    writer.Flush();
 
                     AssertContents("", buffer);
                 }
@@ -890,24 +905,23 @@ null,
         [InlineData(true)]
         public static void WriteValueInsideObject(bool skipValidation)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions))
             {
                 JsonElement root = doc.RootElement;
-                JsonWriterState state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        SkipValidation = skipValidation,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    SkipValidation = skipValidation,
+                };
 
-                Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
                 writer.WriteStartObject();
 
                 if (skipValidation)
                 {
                     foreach (JsonElement val in root.EnumerateArray())
                     {
-                        val.WriteAsValue(ref writer);
+                        val.WriteAsValue(writer);
                     }
 
                     writer.WriteEndObject();
@@ -923,7 +937,7 @@ null,
                     {
                         JsonTestHelper.AssertThrows<InvalidOperationException>(
                             ref writer,
-                            (ref Utf8JsonWriter w) => val.WriteAsValue(ref w));
+                            (ref Utf8JsonWriter w) => val.WriteAsValue(w));
                     }
 
                     writer.WriteEndObject();
@@ -936,20 +950,19 @@ null,
 
         private static void WriteSimpleValue(bool indented, string jsonIn, string jsonOut = null)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (JsonDocument doc = JsonDocument.Parse($" [  {jsonIn}  ]", s_readerOptions))
             {
                 JsonElement target = doc.RootElement[0];
 
-                var state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        Indented = indented,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    Indented = indented,
+                };
 
-                var writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
 
-                target.WriteAsValue(ref writer);
+                target.WriteAsValue(writer);
                 writer.Flush();
 
                 AssertContents(jsonOut ?? jsonIn, buffer);
@@ -962,20 +975,19 @@ null,
             string expectedIndent,
             string expectedMinimal)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (JsonDocument doc = JsonDocument.Parse($" [  {jsonIn}  ]", s_readerOptions))
             {
                 JsonElement target = doc.RootElement[0];
 
-                var state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        Indented = indented,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    Indented = indented,
+                };
 
-                var writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
 
-                target.WriteAsValue(ref writer);
+                target.WriteAsValue(writer);
                 writer.Flush();
 
                 if (indented && s_replaceNewlines)
@@ -1018,21 +1030,20 @@ null,
             string expectedIndent,
             string expectedMinimal)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (JsonDocument doc = JsonDocument.Parse($" [  {jsonIn}  ]", s_readerOptions))
             {
                 JsonElement target = doc.RootElement[0];
 
-                var state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        Indented = indented,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    Indented = indented,
+                };
 
-                var writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
 
                 writer.WriteStartObject();
-                target.WriteAsProperty(propertyName, ref writer);
+                target.WriteAsProperty(propertyName, writer);
                 writer.WriteEndObject();
                 writer.Flush();
 
@@ -1054,21 +1065,20 @@ null,
             string expectedIndent,
             string expectedMinimal)
         {
-            using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024))
+            var buffer = new ArrayBufferWriter<byte>(1024);
             using (JsonDocument doc = JsonDocument.Parse($" [  {jsonIn}  ]", s_readerOptions))
             {
                 JsonElement target = doc.RootElement[0];
 
-                var state = new JsonWriterState(
-                    new JsonWriterOptions
-                    {
-                        Indented = indented,
-                    });
+                var options = new JsonWriterOptions
+                {
+                    Indented = indented,
+                };
 
-                var writer = new Utf8JsonWriter(buffer, state);
+                var writer = new Utf8JsonWriter(buffer, options);
 
                 writer.WriteStartObject();
-                target.WriteAsProperty(propertyName, ref writer);
+                target.WriteAsProperty(propertyName, writer);
                 writer.WriteEndObject();
                 writer.Flush();
 
@@ -1083,14 +1093,14 @@ null,
             }
         }
 
-        private static void AssertContents(string expectedValue, ArrayBufferWriter buffer)
+        private static void AssertContents(string expectedValue, ArrayBufferWriter<byte> buffer)
         {
             Assert.Equal(
                 expectedValue,
                 Encoding.UTF8.GetString(
-                    buffer.Formatted
+                    buffer.WrittenSpan
 #if netstandard
-                        .AsSpan().ToArray()
+                        .ToArray()
 #endif
                     ));
         }
diff --git a/src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs b/src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs
new file mode 100644 (file)
index 0000000..4184481
--- /dev/null
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.Text.Json.Tests
+{
+    public static partial class JsonWriterOptionsTests
+    {
+        [Fact]
+        public static void JsonWriterOptionsDefaultCtor()
+        {
+            JsonWriterOptions options = default;
+
+            var expectedOption = new JsonWriterOptions
+            {
+                Indented = false,
+                SkipValidation = false
+            };
+            Assert.Equal(expectedOption, options);
+        }
+
+        [Fact]
+        public static void JsonWriterOptionsCtor()
+        {
+            var options = new JsonWriterOptions();
+
+            var expectedOption = new JsonWriterOptions
+            {
+                Indented = false,
+                SkipValidation = false
+            };
+            Assert.Equal(expectedOption, options);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public static void JsonWriterOptions(bool indented, bool skipValidation)
+        {
+            var options = new JsonWriterOptions();
+            options.Indented = indented;
+            options.SkipValidation = skipValidation;
+
+            var expectedOption = new JsonWriterOptions
+            {
+                Indented = indented,
+                SkipValidation = skipValidation
+            };
+            Assert.Equal(expectedOption, options);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs b/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs
deleted file mode 100644 (file)
index 87a1670..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Xunit;
-
-namespace System.Text.Json.Tests
-{
-    public static partial class JsonWriterStateTests
-    {
-        [Fact]
-        public static void DefaultJsonWriterState()
-        {
-            JsonWriterState state = default;
-            Assert.Equal(0, state.BytesCommitted);
-            Assert.Equal(0, state.BytesWritten);
-
-            var expectedOption = new JsonWriterOptions
-            {
-                Indented = false,
-                SkipValidation = false
-            };
-            Assert.Equal(expectedOption, state.Options);
-        }
-
-        [Fact]
-        public static void JsonWriterStateDefaultCtor()
-        {
-            var state = new JsonWriterState();
-            Assert.Equal(0, state.BytesCommitted);
-            Assert.Equal(0, state.BytesWritten);
-
-            var expectedOption = new JsonWriterOptions
-            {
-                Indented = false,
-                SkipValidation = false
-            };
-            Assert.Equal(expectedOption, state.Options);
-        }
-
-        [Fact]
-        public static void JsonWriterStateCtor()
-        {
-            var state = new JsonWriterState(options: default);
-            Assert.Equal(0, state.BytesCommitted);
-            Assert.Equal(0, state.BytesWritten);
-
-            var expectedOption = new JsonWriterOptions
-            {
-                Indented = false,
-                SkipValidation = false
-            };
-            Assert.Equal(expectedOption, state.Options);
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/tests/ResizableArray.cs b/src/libraries/System.Text.Json/tests/ResizableArray.cs
deleted file mode 100644 (file)
index b02d6b4..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-
-namespace System.Text.Json.Tests
-{
-    // a List<T> like type designed to be embeded in other types
-    internal struct ResizableArray<T>
-    {
-        public ResizableArray(T[] array, int count = 0)
-        {
-            Array = array;
-            Count = count;
-        }
-
-        public T[] Array { get; set; }
-
-        public int Count { get; set; }
-
-        public int Capacity => Array.Length;
-
-        public T[] Resize(T[] newArray)
-        {
-            T[] oldArray = Array;
-            Array.AsSpan(0, Count).CopyTo(newArray);  // CopyTo will throw if newArray.Length < _count
-            Array = newArray;
-            return oldArray;
-        }
-
-        public ArraySegment<T> Full => new ArraySegment<T>(Array, 0, Count);
-
-        public ArraySegment<T> Free => new ArraySegment<T>(Array, Count, Array.Length - Count);
-
-        public Span<T> FreeSpan => new Span<T>(Array, Count, Array.Length - Count);
-
-        public Memory<T> FreeMemory => new Memory<T>(Array, Count, Array.Length - Count);
-
-        public int FreeCount => Array.Length - Count;
-    }
-}
index 3249795..6133f4a 100644 (file)
@@ -20268,4 +20268,7 @@ tiline\"another\" String\\"],"str":"\"\""}</value>
   }
 }</value>
   </data>
+  <data name="BufferWriterAdvancedTooFar" xml:space="preserve">
+    <value>Cannot advance past the end of the buffer, which has a size of {0}.</value>
+  </data>
 </root>
\ No newline at end of file
index 42f3f71..55b32d1 100644 (file)
@@ -7,7 +7,6 @@
     <Compile Include="$(CommonTestPath)\System\IO\WrappedMemoryStream.cs">
       <Link>CommonTest\System\IO\WrappedMemoryStream.cs</Link>
     </Compile>
-    <Compile Include="ArrayBufferWriter.cs" />
     <Compile Include="BitStackTests.cs" />
     <Compile Include="BufferFactory.cs" />
     <Compile Include="BufferSegment.cs" />
@@ -21,8 +20,7 @@
     <Compile Include="JsonNumberTestData.cs" />
     <Compile Include="JsonReaderStateAndOptionsTests.cs" />
     <Compile Include="JsonTestHelper.cs" />
-    <Compile Include="JsonWriterStateTests.cs" />
-    <Compile Include="ResizableArray.cs" />
+    <Compile Include="JsonWriterOptionsTests.cs" />
     <Compile Include="Serialization\Array.ReadTests.cs" />
     <Compile Include="Serialization\Array.WriteTests.cs" />
     <Compile Include="Serialization\CacheTests.cs" />
   <ItemGroup>
     <Compile Include="..\src\System\Text\Json\BitStack.cs" Link="BitStack.cs" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetsNETStandard)' == 'true'">
+    <Compile Include="$(CommonPath)\System\Buffers\ArrayBufferWriter.cs">
+      <Link>CommonTest\System\Buffers\ArrayBufferWriter.cs</Link>
+    </Compile>
+  </ItemGroup>
   <ItemGroup>
     <ReferenceFromRuntime Include="Newtonsoft.Json" />    
     <!-- Copy external dependency into the test output directory. -->
index 32bf61c..0ce3e88 100644 (file)
@@ -3,9 +3,11 @@
 // See the LICENSE file in the project root for more information.
 
 using Xunit;
+using System.Buffers;
 using System.IO;
 using Newtonsoft.Json;
 using System.Globalization;
+using System.Threading.Tasks;
 
 namespace System.Text.Json.Tests
 {
@@ -20,21 +22,261 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void NullCtor(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            try
-            {
-                var jsonUtf8 = new Utf8JsonWriter(null);
-                Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in.");
-            }
-            catch (ArgumentNullException) { }
+            Assert.Throws<ArgumentNullException>(() => new Utf8JsonWriter((Stream)null));
+            Assert.Throws<ArgumentNullException>(() => new Utf8JsonWriter((IBufferWriter<byte>)null));
+            Assert.Throws<ArgumentNullException>(() => new Utf8JsonWriter((Stream)null, options));
+            Assert.Throws<ArgumentNullException>(() => new Utf8JsonWriter((IBufferWriter<byte>)null, options));
+        }
 
-            try
-            {
-                var jsonUtf8 = new Utf8JsonWriter(null, state);
-                Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in.");
-            }
-            catch (ArgumentNullException) { }
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void CantWriteToNonWritableStream(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var stream = new MemoryStream();
+            stream.Dispose();
+
+            Assert.Throws<ArgumentException>(() => new Utf8JsonWriter(stream));
+            Assert.Throws<ArgumentException>(() => new Utf8JsonWriter(stream, options));
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InitialState(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var stream = new MemoryStream();
+            var writer = new Utf8JsonWriter(stream, options);
+            Assert.Equal(0, writer.BytesCommitted);
+            Assert.Equal(0, writer.BytesPending);
+            Assert.Equal(0, writer.CurrentDepth);
+            Assert.Equal(formatted, writer.Options.Indented);
+            Assert.Equal(skipValidation, writer.Options.SkipValidation);
+            Assert.Equal(0, stream.Position);
+
+            var output = new FixedSizedBufferWriter(0);
+            writer = new Utf8JsonWriter(output, options);
+            Assert.Equal(0, writer.BytesCommitted);
+            Assert.Equal(0, writer.BytesPending);
+            Assert.Equal(0, writer.CurrentDepth);
+            Assert.Equal(formatted, writer.Options.Indented);
+            Assert.Equal(skipValidation, writer.Options.SkipValidation);
+            Assert.Equal(0, output.FormattedCount);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void Reset(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteNumberValue(1);
+            writeToStream.Flush();
+
+            Assert.True(writeToStream.BytesCommitted != 0);
+
+            writeToStream.Reset();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(formatted, writeToStream.Options.Indented);
+            Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
+            Assert.True(stream.Position != 0);
+
+            long previousWritten = stream.Position;
+            writeToStream.Flush();
+            Assert.Equal(previousWritten, stream.Position);
+
+            var output = new FixedSizedBufferWriter(32);
+            var writeToIBW = new Utf8JsonWriter(output, options);
+            writeToIBW.WriteNumberValue(1);
+            writeToIBW.Flush();
+
+            Assert.True(writeToIBW.BytesCommitted != 0);
+
+            writeToIBW.Reset();
+            Assert.Equal(0, writeToIBW.BytesCommitted);
+            Assert.Equal(0, writeToIBW.BytesPending);
+            Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(formatted, writeToIBW.Options.Indented);
+            Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
+            Assert.True(output.FormattedCount != 0);
+
+            previousWritten = output.FormattedCount;
+            writeToIBW.Flush();
+            Assert.Equal(previousWritten, output.FormattedCount);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void ResetWithSameOutput(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteNumberValue(1);
+            writeToStream.Flush();
+
+            Assert.True(writeToStream.BytesCommitted != 0);
+
+            writeToStream.Reset(stream);
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(formatted, writeToStream.Options.Indented);
+            Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
+            Assert.True(stream.Position != 0);
+
+            long previousWritten = stream.Position;
+            writeToStream.Flush();
+            Assert.Equal(previousWritten, stream.Position);
+
+            writeToStream.WriteNumberValue(1);
+            writeToStream.Flush();
+
+            Assert.NotEqual(previousWritten, stream.Position);
+            Assert.Equal("11", Encoding.UTF8.GetString(stream.ToArray()));
+
+            var output = new FixedSizedBufferWriter(32);
+            var writeToIBW = new Utf8JsonWriter(output, options);
+            writeToIBW.WriteNumberValue(1);
+            writeToIBW.Flush();
+
+            Assert.True(writeToIBW.BytesCommitted != 0);
+
+            writeToIBW.Reset(output);
+            Assert.Equal(0, writeToIBW.BytesCommitted);
+            Assert.Equal(0, writeToIBW.BytesPending);
+            Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(formatted, writeToIBW.Options.Indented);
+            Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
+            Assert.True(output.FormattedCount != 0);
+
+            previousWritten = output.FormattedCount;
+            writeToIBW.Flush();
+            Assert.Equal(previousWritten, output.FormattedCount);
+
+            writeToIBW.WriteNumberValue(1);
+            writeToIBW.Flush();
+
+            Assert.NotEqual(previousWritten, output.FormattedCount);
+            Assert.Equal("11", Encoding.UTF8.GetString(output.Formatted));
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void ResetChangeOutputMode(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteNumberValue(1);
+            writeToStream.Flush();
+
+            Assert.True(writeToStream.BytesCommitted != 0);
+
+            var output = new FixedSizedBufferWriter(32);
+            writeToStream.Reset(output);
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(formatted, writeToStream.Options.Indented);
+            Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
+            Assert.True(stream.Position != 0);
+
+            long previousWrittenStream = stream.Position;
+            long previousWrittenIBW = output.FormattedCount;
+            Assert.Equal(0, previousWrittenIBW);
+            writeToStream.Flush();
+            Assert.Equal(previousWrittenStream, stream.Position);
+            Assert.Equal(previousWrittenIBW, output.FormattedCount);
+
+            writeToStream.WriteNumberValue(1);
+            writeToStream.Flush();
+
+            Assert.True(writeToStream.BytesCommitted != 0);
+            Assert.Equal(previousWrittenStream, stream.Position);
+            Assert.True(output.FormattedCount != 0);
+
+            output = new FixedSizedBufferWriter(32);
+            var writeToIBW = new Utf8JsonWriter(output, options);
+            writeToIBW.WriteNumberValue(1);
+            writeToIBW.Flush();
+
+            Assert.True(writeToIBW.BytesCommitted != 0);
+
+            stream = new MemoryStream();
+            writeToIBW.Reset(stream);
+            Assert.Equal(0, writeToIBW.BytesCommitted);
+            Assert.Equal(0, writeToIBW.BytesPending);
+            Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(formatted, writeToIBW.Options.Indented);
+            Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
+            Assert.True(output.FormattedCount != 0);
+
+            previousWrittenStream = stream.Position;
+            previousWrittenIBW = output.FormattedCount;
+            Assert.Equal(0, previousWrittenStream);
+            writeToIBW.Flush();
+            Assert.Equal(previousWrittenStream, stream.Position);
+            Assert.Equal(previousWrittenIBW, output.FormattedCount);
+
+            writeToIBW.WriteNumberValue(1);
+            writeToIBW.Flush();
+
+            Assert.True(writeToIBW.BytesCommitted != 0);
+            Assert.Equal(previousWrittenIBW, output.FormattedCount);
+            Assert.True(stream.Position != 0);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidReset(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+
+            Assert.Throws<ArgumentNullException>(() => writeToStream.Reset((Stream)null));
+            Assert.Throws<ArgumentNullException>(() => writeToStream.Reset((IBufferWriter<byte>)null));
+
+            stream.Dispose();
+
+            Assert.Throws<ArgumentException>(() => writeToStream.Reset(stream));
+
+            var output = new FixedSizedBufferWriter(32);
+            var writeToIBW = new Utf8JsonWriter(output, options);
+
+            Assert.Throws<ArgumentNullException>(() => writeToIBW.Reset((Stream)null));
+            Assert.Throws<ArgumentNullException>(() => writeToIBW.Reset((IBufferWriter<byte>)null));
+
+            Assert.Throws<ArgumentException>(() => writeToIBW.Reset(stream));
         }
 
         [Theory]
@@ -44,25 +286,41 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FlushEmpty(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new FixedSizedBufferWriter(0);
-            try
-            {
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
-                jsonUtf8.Flush();
-                WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload.");
-            }
-            catch (InvalidOperationException) { }
 
-            output = new FixedSizedBufferWriter(10);
-            try
-            {
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
-                jsonUtf8.WriteCommentValue("hi");
-                jsonUtf8.Flush();
-                WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload.");
-            }
-            catch (InvalidOperationException) { }
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.Flush();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.Flush();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, stream.Position);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public async Task FlushEmptyAsync(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(0);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            await jsonUtf8.FlushAsync();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            await writeToStream.FlushAsync();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, stream.Position);
         }
 
         [Theory]
@@ -72,16 +330,39 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FlushMultipleTimes(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new FixedSizedBufferWriter(10);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
             jsonUtf8.WriteStartObject();
             jsonUtf8.WriteEndObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(2, jsonUtf8.BytesPending);
+            Assert.Equal(0, output.FormattedCount);
             jsonUtf8.Flush();
             Assert.Equal(2, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(2, output.FormattedCount);
             jsonUtf8.Flush();
             Assert.Equal(2, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(2, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            writeToStream.WriteEndObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(2, writeToStream.BytesPending);
+            Assert.Equal(0, stream.Position);
+            writeToStream.Flush();
+            Assert.Equal(2, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(2, stream.Position);
+            writeToStream.Flush();
+            Assert.Equal(2, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(2, stream.Position);
         }
 
         [Theory]
@@ -89,20 +370,275 @@ namespace System.Text.Json.Tests
         [InlineData(true, false)]
         [InlineData(false, true)]
         [InlineData(false, false)]
-        public void InvalidBufferWriter(bool formatted, bool skipValidation)
+        public async Task FlushMultipleTimesAsync(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(10);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(2, jsonUtf8.BytesPending);
+            Assert.Equal(0, output.FormattedCount);
+            await jsonUtf8.FlushAsync();
+            Assert.Equal(2, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(2, output.FormattedCount);
+            await jsonUtf8.FlushAsync();
+            Assert.Equal(2, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(2, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            writeToStream.WriteEndObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(2, writeToStream.BytesPending);
+            Assert.Equal(0, stream.Position);
+            await writeToStream.FlushAsync();
+            Assert.Equal(2, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(2, stream.Position);
+            await writeToStream.FlushAsync();
+            Assert.Equal(2, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(2, stream.Position);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void DisposeAutoFlushes(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(10);
 
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, output.FormattedCount);
+            jsonUtf8.Dispose();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(2, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            writeToStream.WriteEndObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, stream.Position);
+            writeToStream.Dispose();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(2, stream.Position);
+        }
+
+#if !netstandard
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public async Task DisposeAutoFlushesAsync(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(10);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, output.FormattedCount);
+            await jsonUtf8.DisposeAsync();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(2, output.FormattedCount);
+
+            var stream = new MemoryStream();
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            writeToStream.WriteEndObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, stream.Position);
+            await writeToStream.DisposeAsync();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(2, stream.Position);
+        }
+#endif
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void UseAfterDisposeInvalid(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(10);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(1, jsonUtf8.BytesPending);
+            Assert.Equal(0, output.FormattedCount);
+            jsonUtf8.Dispose();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(1, output.FormattedCount);
+            Assert.Throws<NullReferenceException>(() => jsonUtf8.Flush());
+            jsonUtf8.Dispose();
+            Assert.Throws<NullReferenceException>(() => jsonUtf8.Flush());
+
+            jsonUtf8.Reset();
+
+            var stream = new MemoryStream();
+            Assert.Throws<ObjectDisposedException>(() => jsonUtf8.Reset(stream));
+
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(1, writeToStream.BytesPending);
+            Assert.Equal(0, stream.Position);
+            writeToStream.Dispose();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(1, stream.Position);
+            Assert.Throws<NullReferenceException>(() => writeToStream.Flush());
+            writeToStream.Dispose();
+            Assert.Throws<NullReferenceException>(() => writeToStream.Flush());
+
+            writeToStream.Reset();
+
+            Assert.Throws<ObjectDisposedException>(() => jsonUtf8.Reset(output));
+        }
+
+#if !netstandard
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public async Task UseAfterDisposeInvalidAsync(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new FixedSizedBufferWriter(10);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(1, jsonUtf8.BytesPending);
+            Assert.Equal(0, output.FormattedCount);
+            await jsonUtf8.DisposeAsync();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
+            Assert.Equal(1, output.FormattedCount);
+            await Assert.ThrowsAsync<NullReferenceException>(() => jsonUtf8.FlushAsync());
+            await jsonUtf8.DisposeAsync();
+            await Assert.ThrowsAsync<NullReferenceException>(() => jsonUtf8.FlushAsync());
+
+            jsonUtf8.Reset();
+
+            var stream = new MemoryStream();
+            Assert.Throws<ObjectDisposedException>(() => jsonUtf8.Reset(stream));
+
+            var writeToStream = new Utf8JsonWriter(stream, options);
+            writeToStream.WriteStartObject();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(1, writeToStream.BytesPending);
+            Assert.Equal(0, stream.Position);
+            await writeToStream.DisposeAsync();
+            Assert.Equal(0, writeToStream.BytesCommitted);
+            Assert.Equal(0, writeToStream.BytesPending);
+            Assert.Equal(1, stream.Position);
+            await Assert.ThrowsAsync<NullReferenceException>(() => writeToStream.FlushAsync());
+            await writeToStream.DisposeAsync();
+            await Assert.ThrowsAsync<NullReferenceException>(() => writeToStream.FlushAsync());
+
+            writeToStream.Reset();
+
+            Assert.Throws<ObjectDisposedException>(() => jsonUtf8.Reset(output));
+        }
+#endif
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidBufferWriter(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new InvalidBufferWriter();
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
-            try
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteNumberValue((ulong)12345678901));
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public async Task WriteLargeToStream(bool formatted, bool skipValidation)
+        {
+            var stream = new MemoryStream();
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            await WriteLargeToStreamHelper(stream, options);
+
+            string expectedString = GetExpectedLargeString(formatted);
+            string actualString = Encoding.UTF8.GetString(stream.ToArray());
+
+            Assert.Equal(expectedString, actualString);
+        }
+
+        private static async Task WriteLargeToStreamHelper(Stream stream, JsonWriterOptions options)
+        {
+            const int SyncWriteThreshold = 25_000;
+
+#if !netstandard
+            await
+#endif
+                using var jsonUtf8 = new Utf8JsonWriter(stream, options);
+
+            byte[] utf8String = Encoding.UTF8.GetBytes("some string 1234");
+
+            jsonUtf8.WriteStartArray();
+            for (int i = 0; i < 10_000; i++)
             {
-                jsonUtf8.WriteNumberValue((ulong)12345678901);
-                Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
+                jsonUtf8.WriteStringValue(utf8String);
+                if (jsonUtf8.BytesPending > SyncWriteThreshold)
+                {
+                    await jsonUtf8.FlushAsync();
+                }
             }
-            catch (ArgumentException) { }
+            jsonUtf8.WriteEndArray();
+        }
+
+        private static string GetExpectedLargeString(bool prettyPrint)
+        {
+            var ms = new MemoryStream();
+            TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+            var json = new JsonTextWriter(streamWriter)
+            {
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None
+            };
+
+            json.WriteStartArray();
+            for (int i = 0; i < 10_000; i++)
+            {
+                json.WriteValue("some string 1234");
+            }
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
         }
 
         [Theory]
@@ -112,23 +648,16 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new FixedSizedBufferWriter(37);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
             Guid guid = Guid.NewGuid();
 
-            try
-            {
-                jsonUtf8.WriteStringValue(guid);
-                Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
-            }
-            catch (ArgumentException) { }
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStringValue(guid));
 
-            output = new FixedSizedBufferWriter(39);
-            jsonUtf8 = new Utf8JsonWriter(output, state);
+            output = new FixedSizedBufferWriter(41);
+            jsonUtf8 = new Utf8JsonWriter(output, options);
             jsonUtf8.WriteStringValue(guid);
             jsonUtf8.Flush();
             string actualStr = Encoding.UTF8.GetString(output.Formatted);
@@ -144,23 +673,16 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new FixedSizedBufferWriter(20);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
             var date = new DateTime(2019, 1, 1);
 
-            try
-            {
-                jsonUtf8.WriteStringValue(date);
-                Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
-            }
-            catch (ArgumentException) { }
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStringValue(date));
 
-            output = new FixedSizedBufferWriter(21);
-            jsonUtf8 = new Utf8JsonWriter(output, state);
+            output = new FixedSizedBufferWriter(38);
+            jsonUtf8 = new Utf8JsonWriter(output, options);
             jsonUtf8.WriteStringValue(date);
             jsonUtf8.Flush();
             string actualStr = Encoding.UTF8.GetString(output.Formatted);
@@ -176,23 +698,16 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var output = new FixedSizedBufferWriter(26);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
             DateTimeOffset date = new DateTime(2019, 1, 1);
 
-            try
-            {
-                jsonUtf8.WriteStringValue(date);
-                Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
-            }
-            catch (ArgumentException) { }
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStringValue(date));
 
-            output = new FixedSizedBufferWriter(27);
-            jsonUtf8 = new Utf8JsonWriter(output, state);
+            output = new FixedSizedBufferWriter(38);
+            jsonUtf8 = new Utf8JsonWriter(output, options);
             jsonUtf8.WriteStringValue(date);
             jsonUtf8.Flush();
             string actualStr = Encoding.UTF8.GetString(output.Formatted);
@@ -208,14 +723,15 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation)
         {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
             var random = new Random(42);
 
             for (int i = 0; i < 1_000; i++)
             {
-                var output = new FixedSizedBufferWriter(31);
+                var output = new FixedSizedBufferWriter(34);
                 decimal value = JsonTestHelper.NextDecimal(random, 78E14, -78E14);
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteNumberValue(value);
 
                 jsonUtf8.Flush();
@@ -227,10 +743,9 @@ namespace System.Text.Json.Tests
 
             for (int i = 0; i < 1_000; i++)
             {
-                var output = new FixedSizedBufferWriter(31);
+                var output = new FixedSizedBufferWriter(34);
                 decimal value = JsonTestHelper.NextDecimal(random, 1_000_000, -1_000_000);
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteNumberValue(value);
 
                 jsonUtf8.Flush();
@@ -241,10 +756,9 @@ namespace System.Text.Json.Tests
             }
 
             {
-                var output = new FixedSizedBufferWriter(31);
+                var output = new FixedSizedBufferWriter(34);
                 decimal value = 9999999999999999999999999999m;
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteNumberValue(value);
 
                 jsonUtf8.Flush();
@@ -255,10 +769,9 @@ namespace System.Text.Json.Tests
             }
 
             {
-                var output = new FixedSizedBufferWriter(31);
+                var output = new FixedSizedBufferWriter(34);
                 decimal value = -9999999999999999999999999999m;
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteNumberValue(value);
 
                 jsonUtf8.Flush();
@@ -271,18 +784,12 @@ namespace System.Text.Json.Tests
             {
                 var output = new FixedSizedBufferWriter(30);
                 decimal value = -0.9999999999999999999999999999m;
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
-                try
-                {
-                    jsonUtf8.WriteNumberValue(value);
-                    Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
-                }
-                catch (ArgumentException) { }
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteNumberValue(value));
 
-                output = new FixedSizedBufferWriter(31);
-                jsonUtf8 = new Utf8JsonWriter(output, state);
+                output = new FixedSizedBufferWriter(34);
+                jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteNumberValue(value);
 
                 jsonUtf8.Flush();
@@ -300,181 +807,256 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void InvalidJsonMismatch(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(1024);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            if (skipValidation)
             {
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            if (skipValidation)
             {
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            if (skipValidation)
+            {
+                jsonUtf8.WriteStartArray("property at start");
+            }
+            else
             {
-                jsonUtf8.WriteStartArray("property at start", escape: false);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray("property at start"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject("property at start", escape: false);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteStartObject("property at start");
+            }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject("property at start"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartArray("property inside array", escape: false);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteStartArray("property inside array");
+            }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray("property inside array"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            if (skipValidation)
             {
                 jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteStringValue("key");
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStringValue("key"));
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
                 jsonUtf8.WriteString("key", "value");
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteString("key", "value"));
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteEndArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteEndArray();
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteStartObject("some object");
+            jsonUtf8.WriteEndObject();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartObject("some object", escape: false);
-                jsonUtf8.WriteEndObject();
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartObject("some object", escape: false);
-                jsonUtf8.WriteEndObject();
+                jsonUtf8.WriteStartObject("some object");
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject("some object"));
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteStartArray("test array");
+            jsonUtf8.WriteEndArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartArray("test array", escape: false);
-                jsonUtf8.WriteEndArray();
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteEndArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteEndArray();
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteEndObject();
-                jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteStartArray();
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartArray();
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteStartObject("test object");
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartObject("test object", escape: false);
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidJsonIncomplete(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            Assert.True(jsonUtf8.CurrentDepth != 0);
+
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.True(jsonUtf8.CurrentDepth != 0);
+
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteEndArray();
+            Assert.True(jsonUtf8.CurrentDepth != 0);
+
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteStartObject("some object");
+            Assert.True(jsonUtf8.CurrentDepth != 0);
+
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            Assert.True(jsonUtf8.CurrentDepth != 0);
 
-            output.Dispose();
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteStartArray("test array");
+            jsonUtf8.WriteEndArray();
+            Assert.True(jsonUtf8.CurrentDepth != 0);
         }
 
         [Theory]
@@ -482,160 +1064,98 @@ namespace System.Text.Json.Tests
         [InlineData(true, false)]
         [InlineData(false, true)]
         [InlineData(false, false)]
-        public void InvalidJsonIncomplete(bool formatted, bool skipValidation)
+        public void InvalidJsonPrimitive(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var output = new ArrayBufferWriter(1024);
-
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteNumberValue(12345);
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteNumberValue(12345));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
                 jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteEndArray();
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartObject("some object", escape: false);
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray());
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStartObject("some object", escape: false);
-                jsonUtf8.WriteEndObject();
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteStartObject();
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartArray("test array", escape: false);
-                jsonUtf8.WriteEndArray();
-                jsonUtf8.Flush(isFinalBlock: true);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject());
             }
-            catch (InvalidOperationException) { }
-
-            output.Dispose();
-        }
-
-        [Theory]
-        [InlineData(true, true)]
-        [InlineData(true, false)]
-        [InlineData(false, true)]
-        [InlineData(false, false)]
-        public void InvalidJsonPrimitive(bool formatted, bool skipValidation)
-        {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteNumberValue(12345);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteStartArray("property name");
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteStartArray();
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray("property name"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteStartObject();
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteStartObject("property name");
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteStartArray("property name", escape: false);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject("property name"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteStartObject("property name", escape: false);
-                WriterDidNotThrow(skipValidation);
+                jsonUtf8.WriteString("property name", "value");
             }
-            catch (InvalidOperationException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            else
             {
-                jsonUtf8.WriteNumberValue(12345);
-                jsonUtf8.WriteString("property name", "value", escape: false);
-                WriterDidNotThrow(skipValidation);
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteString("property name", "value"));
             }
-            catch (InvalidOperationException) { }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteNumberValue(12345);
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+            }
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteNumberValue(12345);
+            if (skipValidation)
             {
-                jsonUtf8.WriteNumberValue(12345);
                 jsonUtf8.WriteEndObject();
-                WriterDidNotThrow(skipValidation);
             }
-            catch (InvalidOperationException) { }
-
-            output.Dispose();
+            else
+            {
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+            }
         }
 
         [Theory]
@@ -645,113 +1165,50 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void InvalidNumbersJson(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(1024);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(double.NegativeInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(double.PositiveInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(double.NegativeInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(double.NaN);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(double.PositiveInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(float.PositiveInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(double.NaN));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(float.NegativeInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(float.PositiveInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteNumberValue(float.NaN);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(float.NegativeInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", double.NegativeInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumberValue(float.NaN));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", double.PositiveInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", double.NegativeInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", double.NaN);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", double.PositiveInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", float.PositiveInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", double.NaN));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", float.NegativeInfinity);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", float.PositiveInfinity));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteNumber("name", float.NaN);
-                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
-            }
-            catch (ArgumentException) { }
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", float.NegativeInfinity));
 
-            output.Dispose();
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteNumber("name", float.NaN));
         }
 
         [Theory]
@@ -759,21 +1216,19 @@ namespace System.Text.Json.Tests
         [InlineData(false)]
         public void InvalidJsonContinueShouldSucceed(bool formatted)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
-
-            var output = new ArrayBufferWriter(1024);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             for (int i = 0; i < 100; i++)
+            {
                 jsonUtf8.WriteEndArray();
+            }
             jsonUtf8.WriteStartArray();
             jsonUtf8.WriteEndArray();
             jsonUtf8.Flush();
 
-            ArraySegment<byte> arraySegment = output.Formatted;
-            string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
             var sb = new StringBuilder();
             for (int i = 0; i < 100; i++)
             {
@@ -786,9 +1241,7 @@ namespace System.Text.Json.Tests
                 sb.Append(Environment.NewLine);
             sb.Append("[]");
 
-            Assert.Equal(sb.ToString(), actualStr);
-
-            output.Dispose();
+            AssertContents(sb.ToString(), output);
         }
 
         [Theory]
@@ -798,22 +1251,17 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WritingTooDeep(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var output = new ArrayBufferWriter(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-            try
+            for (int i = 0; i < 1000; i++)
             {
-                for (int i = 0; i < 1001; i++)
-                {
-                    jsonUtf8.WriteStartArray();
-                }
-                Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000.");
+                jsonUtf8.WriteStartArray();
             }
-            catch (InvalidOperationException) { }
-
-            output.Dispose();
+            Assert.Equal(1000, jsonUtf8.CurrentDepth);
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray());
         }
 
         [Theory]
@@ -823,37 +1271,25 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WritingTooDeepProperty(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(1024);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            try
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            for (int i = 0; i < 999; i++)
             {
-                jsonUtf8.WriteStartObject();
-                for (int i = 0; i < 1000; i++)
-                {
-                    jsonUtf8.WriteStartArray("name");
-                }
-                Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000.");
+                jsonUtf8.WriteStartObject("name");
             }
-            catch (InvalidOperationException) { }
+            Assert.Equal(1000, jsonUtf8.CurrentDepth);
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray("name"));
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            try
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            for (int i = 0; i < 999; i++)
             {
-                jsonUtf8.WriteStartObject();
-                for (int i = 0; i < 1000; i++)
-                {
-                    jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name"));
-                }
-                Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000.");
+                jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("name"));
             }
-            catch (InvalidOperationException) { }
-
-            output.Dispose();
+            Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name")));
         }
 
         [ConditionalTheory(nameof(IsX64))]
@@ -864,14 +1300,8 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WritingTooLargeProperty(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(1024);
-
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            Span<byte> key;
-            Span<char> keyChars;
+            byte[] key;
+            char[] keyChars;
 
             try
             {
@@ -883,28 +1313,19 @@ namespace System.Text.Json.Tests
                 return;
             }
 
-            key.Fill((byte)'a');
-            keyChars.Fill('a');
-
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartArray(keyChars);
-                Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {keyChars.Length}");
-            }
-            catch (ArgumentException) { }
+            key.AsSpan().Fill((byte)'a');
+            keyChars.AsSpan().Fill('a');
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            try
-            {
-                jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteStartArray(key);
-                Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {key.Length}");
-            }
-            catch (ArgumentException) { }
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteStartArray(keyChars));
 
-            output.Dispose();
+            jsonUtf8 = new Utf8JsonWriter(output, options);
+            jsonUtf8.WriteStartObject();
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteStartArray(key));
         }
 
         [Theory]
@@ -916,24 +1337,16 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = "123456789012345";
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 3; i++)
-            {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
-
-                jsonUtf8.WriteNumberValue(123456789012345);
-
-                jsonUtf8.Flush();
+            var output = new ArrayBufferWriter<byte>(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+            jsonUtf8.WriteNumberValue(123456789012345);
 
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
+            jsonUtf8.Flush();
 
-                output.Dispose();
-            }
+            AssertContents(expectedStr, output);
         }
 
         [Theory]
@@ -943,58 +1356,144 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WriteHelloWorld(bool formatted, bool skipValidation)
         {
-            string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted);
+            string propertyName = "message";
+            string value = "Hello, World!";
+            string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted, propertyName, value);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             for (int i = 0; i < 9; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(32);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteString("message", "Hello, World!", escape: false);
+                        jsonUtf8.WriteString("message", "Hello, World!");
+                        jsonUtf8.WriteString("message", "Hello, World!");
                         break;
                     case 1:
-                        jsonUtf8.WriteString("message", "Hello, World!".AsSpan(), escape: false);
+                        jsonUtf8.WriteString("message", "Hello, World!".AsSpan());
+                        jsonUtf8.WriteString("message", "Hello, World!".AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"));
+                        jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"));
                         break;
                     case 3:
-                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!", escape: false);
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!");
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!");
                         break;
                     case 4:
-                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan(), escape: false);
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan());
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan());
                         break;
                     case 5:
-                        jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"));
+                        jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"));
                         break;
                     case 6:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!", escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!");
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!");
                         break;
                     case 7:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan(), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan());
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan());
                         break;
                     case 8:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"));
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"));
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+                AssertContents(expectedStr, output);
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteHelloWorldEscaped(bool formatted, bool skipValidation)
+        {
+            string propertyName = "mess><age";
+            string value = "Hello,>< World!";
+            string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted, propertyName, value);
+
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            ReadOnlySpan<char> propertyNameSpan = propertyName.AsSpan();
+            ReadOnlySpan<char> valueSpan = value.AsSpan();
+            ReadOnlySpan<byte> propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName);
+            ReadOnlySpan<byte> valueSpanUtf8 = Encoding.UTF8.GetBytes(value);
+
+            for (int i = 0; i < 9; i++)
+            {
+                var output = new ArrayBufferWriter<byte>(32);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(propertyName, value);
+                        jsonUtf8.WriteString(propertyName, value);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteString(propertyName, valueSpan);
+                        jsonUtf8.WriteString(propertyName, valueSpan);
+                        break;
+                    case 2:
+                        jsonUtf8.WriteString(propertyName, valueSpanUtf8);
+                        jsonUtf8.WriteString(propertyName, valueSpanUtf8);
+                        break;
+                    case 3:
+                        jsonUtf8.WriteString(propertyNameSpan, value);
+                        jsonUtf8.WriteString(propertyNameSpan, value);
+                        break;
+                    case 4:
+                        jsonUtf8.WriteString(propertyNameSpan, valueSpan);
+                        jsonUtf8.WriteString(propertyNameSpan, valueSpan);
+                        break;
+                    case 5:
+                        jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8);
+                        jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8);
+                        break;
+                    case 6:
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, value);
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, value);
+                        break;
+                    case 7:
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan);
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan);
+                        break;
+                    case 8:
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8);
+                        jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8);
+                        break;
+                }
 
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush();
 
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
+
+            // Verify that escaping does not change the input strings/spans.
+            Assert.Equal("mess><age", propertyName);
+            Assert.Equal("Hello,>< World!", value);
+            Assert.True(propertyName.AsSpan().SequenceEqual(propertyNameSpan));
+            Assert.True(value.AsSpan().SequenceEqual(valueSpan));
+            Assert.True(Encoding.UTF8.GetBytes(propertyName).AsSpan().SequenceEqual(propertyNameSpanUtf8));
+            Assert.True(Encoding.UTF8.GetBytes(value).AsSpan().SequenceEqual(valueSpanUtf8));
         }
 
         [Theory]
@@ -1004,32 +1503,32 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WritePartialHelloWorld(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            var output = new ArrayBufferWriter(10);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var output = new ArrayBufferWriter<byte>(10);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             jsonUtf8.WriteStartObject();
 
             Assert.Equal(0, jsonUtf8.BytesCommitted);
-            Assert.Equal(1, jsonUtf8.BytesWritten);
+            Assert.Equal(1, jsonUtf8.BytesPending);
 
             jsonUtf8.WriteString("message", "Hello, World!");
 
-            Assert.Equal(16, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
             if (formatted)
-                Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesWritten);
+                Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space
             else
-                Assert.Equal(26, jsonUtf8.BytesWritten);
+                Assert.Equal(26, jsonUtf8.BytesPending);
 
-            jsonUtf8.Flush(isFinalBlock: false);
+            jsonUtf8.Flush();
 
             if (formatted)
                 Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space
             else
                 Assert.Equal(26, jsonUtf8.BytesCommitted);
 
-            Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten);
+            Assert.Equal(0, jsonUtf8.BytesPending);
 
             jsonUtf8.WriteString("message", "Hello, World!");
             jsonUtf8.WriteEndObject();
@@ -1040,83 +1539,18 @@ namespace System.Text.Json.Tests
                 Assert.Equal(26, jsonUtf8.BytesCommitted);
 
             if (formatted)
-                Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesWritten); // new lines, indentation, white space
+                Assert.Equal(27 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space
             else
-                Assert.Equal(53, jsonUtf8.BytesWritten);
+                Assert.Equal(27, jsonUtf8.BytesPending);
 
-            jsonUtf8.Flush(isFinalBlock: true);
+            jsonUtf8.Flush();
 
             if (formatted)
                 Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space
             else
                 Assert.Equal(53, jsonUtf8.BytesCommitted);
 
-            Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten);
-
-            Assert.Equal(0, state.BytesCommitted);
-            Assert.Equal(0, state.BytesWritten);
-
-            state = jsonUtf8.GetCurrentState();
-            Assert.Equal(jsonUtf8.BytesCommitted, state.BytesCommitted);
-            Assert.Equal(jsonUtf8.BytesWritten, state.BytesWritten);
-
-            output.Dispose();
-        }
-
-        [Theory]
-        [InlineData(true, true)]
-        [InlineData(true, false)]
-        [InlineData(false, true)]
-        [InlineData(false, false)]
-        public void WritePartialHelloWorldSaveState(bool formatted, bool skipValidation)
-        {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(10);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            Assert.Equal(0, jsonUtf8.CurrentDepth);
-            jsonUtf8.WriteStartObject();
-            Assert.Equal(1, jsonUtf8.CurrentDepth);
-            jsonUtf8.Flush(isFinalBlock: false);
-
-            state = jsonUtf8.GetCurrentState();
-
-            Assert.Equal(1, state.BytesCommitted);
-            Assert.Equal(1, state.BytesWritten);
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            Assert.Equal(1, jsonUtf8.CurrentDepth);
-
-            jsonUtf8.WriteString("message", "Hello, World!");
-            jsonUtf8.WriteEndObject();
-            jsonUtf8.Flush();
-
-            Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten);
-
-            if (formatted)
-                Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesCommitted);
-            else
-                Assert.Equal(26, jsonUtf8.BytesCommitted);
-
-            Assert.Equal(1, state.BytesCommitted);
-            Assert.Equal(1, state.BytesWritten);
-
-            state = jsonUtf8.GetCurrentState();
-
-            if (formatted)
-            {
-                Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesCommitted);
-                Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesWritten);
-            }
-            else
-            {
-                Assert.Equal(26, state.BytesCommitted);
-                Assert.Equal(26, state.BytesWritten);
-            }
-
-            output.Dispose();
+            Assert.Equal(0, jsonUtf8.BytesPending);
         }
 
         [Theory]
@@ -1126,88 +1560,30 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WriteInvalidPartialJson(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(10);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(10);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             jsonUtf8.WriteStartObject();
 
-            Assert.Equal(0, state.BytesCommitted);
-            Assert.Equal(0, state.BytesWritten);
-
-            jsonUtf8.Flush(isFinalBlock: false);
-
-            state = jsonUtf8.GetCurrentState();
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(1, jsonUtf8.BytesPending);
 
-            Assert.Equal(1, state.BytesCommitted);
-            Assert.Equal(1, state.BytesWritten);
+            jsonUtf8.Flush();
 
-            jsonUtf8 = new Utf8JsonWriter(output, state);
+            Assert.Equal(1, jsonUtf8.BytesCommitted);
+            Assert.Equal(0, jsonUtf8.BytesPending);
 
-            try
+            if (skipValidation)
             {
                 jsonUtf8.WriteStringValue("Hello, World!");
-                WriterDidNotThrow(skipValidation);
-            }
-            catch (InvalidOperationException) { }
-            try
-            {
                 jsonUtf8.WriteEndArray();
-                WriterDidNotThrow(skipValidation);
-            }
-            catch (InvalidOperationException) { }
-
-            output.Dispose();
-        }
-
-        [Theory]
-        [InlineData(true, true)]
-        [InlineData(true, false)]
-        [InlineData(false, true)]
-        [InlineData(false, false)]
-        public void WritePartialJsonSkipFlush(bool formatted, bool skipValidation)
-        {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(10);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
-
-            jsonUtf8.WriteStartObject();
-            jsonUtf8.WriteString("message", "Hello, World!");
-
-            Assert.Equal(1, jsonUtf8.CurrentDepth);
-
-            try
-            {
-                state = jsonUtf8.GetCurrentState();
-                Assert.True(false, "Expected InvalidOperationException when trying to get current state without flushing first.");
             }
-            catch (InvalidOperationException)
-            {
-
-            }
-            finally
+            else
             {
-                jsonUtf8.Flush(isFinalBlock: false);
-                state = jsonUtf8.GetCurrentState();
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStringValue("Hello, World!"));
+                Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
             }
-
-            if (formatted)
-                Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, state.BytesWritten);
-            else
-                Assert.Equal(26, state.BytesWritten);
-
-            Assert.Equal(jsonUtf8.BytesWritten, jsonUtf8.BytesCommitted);
-
-            jsonUtf8 = new Utf8JsonWriter(output, state);
-            Assert.Equal(1, jsonUtf8.CurrentDepth);
-            Assert.Equal(0, jsonUtf8.BytesWritten);
-            Assert.Equal(0, jsonUtf8.BytesCommitted);
-            jsonUtf8.WriteEndObject();
-            jsonUtf8.Flush();
-
-            output.Dispose();
         }
 
         [Theory]
@@ -1218,10 +1594,9 @@ namespace System.Text.Json.Tests
         public void WriteInvalidDepthPartial(bool formatted, bool skipValidation)
         {
             {
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-                var output = new ArrayBufferWriter(10);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+                var output = new ArrayBufferWriter<byte>(10);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteEndObject();
@@ -1229,43 +1604,33 @@ namespace System.Text.Json.Tests
 
                 Assert.Equal(0, jsonUtf8.CurrentDepth);
 
-                state = jsonUtf8.GetCurrentState();
-
-                jsonUtf8 = new Utf8JsonWriter(output, state);
-
-                try
+                if (skipValidation)
                 {
                     jsonUtf8.WriteStartObject();
-                    WriterDidNotThrow(skipValidation);
                 }
-                catch (InvalidOperationException) { }
-
-                output.Dispose();
+                else
+                {
+                    Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject());
+                }
             }
 
             {
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-                var output = new ArrayBufferWriter(10);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+                var output = new ArrayBufferWriter<byte>(10);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                Assert.Equal(0, jsonUtf8.CurrentDepth);
-                state = jsonUtf8.GetCurrentState();
-
-                jsonUtf8 = new Utf8JsonWriter(output, state);
-
-                try
+                if (skipValidation)
                 {
                     jsonUtf8.WriteStartObject("name");
-                    WriterDidNotThrow(skipValidation);
                 }
-                catch (InvalidOperationException) { }
-
-                output.Dispose();
+                else
+                {
+                    Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteStartObject("name"));
+                }
             }
         }
 
@@ -1286,18 +1651,18 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetCommentExpectedString(prettyPrint: formatted, comment);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(32);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartArray();
 
                 for (int j = 0; j < 10; j++)
                 {
-                    WriteCommentValue(ref jsonUtf8, i, comment);
+                    WriteCommentValue(jsonUtf8, i, comment);
                 }
 
                 switch (i)
@@ -1313,32 +1678,27 @@ namespace System.Text.Json.Tests
                         break;
                 }
 
-                WriteCommentValue(ref jsonUtf8, i, comment);
+                WriteCommentValue(jsonUtf8, i, comment);
 
                 jsonUtf8.WriteEndArray();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
-        private static void WriteCommentValue(ref Utf8JsonWriter jsonUtf8, int i, string comment)
+        private static void WriteCommentValue(Utf8JsonWriter jsonUtf8, int i, string comment)
         {
             switch (i)
             {
                 case 0:
-                    jsonUtf8.WriteCommentValue(comment, escape: false);
+                    jsonUtf8.WriteCommentValue(comment);
                     break;
                 case 1:
-                    jsonUtf8.WriteCommentValue(comment.AsSpan(), escape: false);
+                    jsonUtf8.WriteCommentValue(comment.AsSpan());
                     break;
                 case 2:
-                    jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment), escape: false);
+                    jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment));
                     break;
             }
         }
@@ -1348,17 +1708,102 @@ namespace System.Text.Json.Tests
         [InlineData(true, false)]
         [InlineData(false, true)]
         [InlineData(false, false)]
+        public void WriteInvalidComment(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>(32);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            string comment = "comment is */ invalid";
+
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteCommentValue(comment));
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteCommentValue(comment.AsSpan()));
+            Assert.Throws<ArgumentException>(() => jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)));
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteCommentsInvalidTextAllowed(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>(32);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            string comment = "comment is * / valid";
+            jsonUtf8.WriteCommentValue(comment);
+            jsonUtf8.WriteCommentValue(comment.AsSpan());
+            jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment));
+
+            comment = "comment is /* valid";
+            jsonUtf8.WriteCommentValue(comment);
+            jsonUtf8.WriteCommentValue(comment.AsSpan());
+            jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment));
+
+            comment = "comment is / * valid even with unpaired surrogate \udc00 this part no longer visible";
+            jsonUtf8.WriteCommentValue(comment);
+            jsonUtf8.WriteCommentValue(comment.AsSpan());
+
+            jsonUtf8.Flush();
+
+            // Explicitly skipping flushing here
+            var invalidUtf8 = new byte[2] { 0xc3, 0x28 };
+            jsonUtf8.WriteCommentValue(invalidUtf8);
+
+            string expectedStr = GetCommentExpectedString(prettyPrint: formatted);
+            AssertContents(expectedStr, output);
+        }
+
+        private static string GetCommentExpectedString(bool prettyPrint)
+        {
+            var ms = new MemoryStream();
+            TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+            var json = new JsonTextWriter(streamWriter)
+            {
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
+                StringEscapeHandling = StringEscapeHandling.EscapeHtml,
+            };
+
+            string comment = "comment is * / valid";
+            json.WriteComment(comment);
+            json.WriteComment(comment);
+            json.WriteComment(comment);
+
+            comment = "comment is /* valid";
+            json.WriteComment(comment);
+            json.WriteComment(comment);
+            json.WriteComment(comment);
+
+            comment = "comment is / * valid even with unpaired surrogate ";
+            json.WriteComment(comment);
+            json.WriteComment(comment);
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
         public void WriteStrings(bool formatted, bool skipValidation)
         {
             string value = "temp";
             string expectedStr = GetStringsExpectedString(prettyPrint: formatted, value);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartArray();
 
@@ -1375,27 +1820,13 @@ namespace System.Text.Json.Tests
                         case 2:
                             jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value));
                             break;
-                        case 3:
-                            jsonUtf8.WriteStringValue(value, escape: false);
-                            break;
-                        case 4:
-                            jsonUtf8.WriteStringValue(value.AsSpan(), escape: false);
-                            break;
-                        case 5:
-                            jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value), escape: false);
-                            break;
                     }
                 }
 
                 jsonUtf8.WriteEndArray();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1423,87 +1854,51 @@ namespace System.Text.Json.Tests
         public void WriteHelloWorldEscaped(bool formatted, bool skipValidation, string key, string value)
         {
             string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml);
-            string expectedStrNoEscape = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 18; i++)
+            for (int i = 0; i < 9; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteString(key, value, escape: true);
+                        jsonUtf8.WriteString(key, value);
                         break;
                     case 1:
-                        jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: true);
+                        jsonUtf8.WriteString(key.AsSpan(), value.AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: true);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value));
                         break;
                     case 3:
-                        jsonUtf8.WriteString(key, value.AsSpan(), escape: true);
+                        jsonUtf8.WriteString(key, value.AsSpan());
                         break;
                     case 4:
-                        jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: true);
+                        jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value));
                         break;
                     case 5:
-                        jsonUtf8.WriteString(key.AsSpan(), value, escape: true);
+                        jsonUtf8.WriteString(key.AsSpan(), value);
                         break;
                     case 6:
-                        jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: true);
+                        jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value));
                         break;
                     case 7:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: true);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value);
                         break;
                     case 8:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: true);
-                        break;
-                    case 9:
-                        jsonUtf8.WriteString(key, value, escape: false);
-                        break;
-                    case 10:
-                        jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: false);
-                        break;
-                    case 11:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: false);
-                        break;
-                    case 12:
-                        jsonUtf8.WriteString(key, value.AsSpan(), escape: false);
-                        break;
-                    case 13:
-                        jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: false);
-                        break;
-                    case 14:
-                        jsonUtf8.WriteString(key.AsSpan(), value, escape: false);
-                        break;
-                    case 15:
-                        jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: false);
-                        break;
-                    case 16:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: false);
-                        break;
-                    case 17:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan());
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i >= 9)
-                    Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-                else
-                    Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1534,41 +1929,28 @@ namespace System.Text.Json.Tests
 
             string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-            for (int i = 0; i < 4; i++)
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            for (int i = 0; i < 2; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        jsonUtf8.WriteString(propertyName, value);
                         break;
                     case 1:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true);
-                        break;
-                    case 2:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false);
-                        jsonUtf8.WriteString(propertyName, value, escape: false);
-                        break;
-                    case 3:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false);
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value));
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1595,41 +1977,28 @@ namespace System.Text.Json.Tests
 
             string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-            for (int i = 0; i < 4; i++)
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            for (int i = 0; i < 2; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        jsonUtf8.WriteString(propertyName, value);
                         break;
                     case 1:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true);
-                        break;
-                    case 2:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false);
-                        jsonUtf8.WriteString(propertyName, value, escape: false);
-                        break;
-                    case 3:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false);
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value));
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1647,41 +2016,28 @@ namespace System.Text.Json.Tests
 
             string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-            for (int i = 0; i < 4; i++)
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            for (int i = 0; i < 2; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        jsonUtf8.WriteString(propertyName, value);
                         break;
                     case 1:
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true);
-                        break;
-                    case 2:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false);
-                        jsonUtf8.WriteString(propertyName, value, escape: false);
-                        break;
-                    case 3:
-                        expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false);
-                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false);
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value));
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1692,58 +2048,73 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void InvalidUTF8(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            var output = new ArrayBufferWriter(1024);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var output = new ArrayBufferWriter<byte>(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            var validUtf8 = new byte[2] { 0xc3, 0xb1 }; // 0xF1
+            var invalidUtf8 = new byte[2] { 0xc3, 0x28 };
 
             jsonUtf8.WriteStartObject();
-            for (int i = 0; i < 8; i++)
+            for (int i = 0; i < 4; i++)
             {
-                try
+                switch (i)
                 {
-                    switch (i)
-                    {
-                        case 0:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: false);
-                            AssertWriterThrow(noThrow: false);
-                            break;
-                        case 1:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: false);
-                            AssertWriterThrow(noThrow: true);
-                            break;
-                        case 2:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: false);
-                            AssertWriterThrow(noThrow: false);
-                            break;
-                        case 3:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: false);
-                            AssertWriterThrow(noThrow: true);
-                            break;
-                        case 4:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: true);
-                            AssertWriterThrow(noThrow: false);
-                            break;
-                        case 5:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: true);
-                            AssertWriterThrow(noThrow: false);
-                            break;
-                        case 6:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: true);
-                            AssertWriterThrow(noThrow: false);
-                            break;
-                        case 7:
-                            jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: true);
-                            AssertWriterThrow(noThrow: true);
-                            break;
-                    }
+                    case 0:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf8, invalidUtf8));
+                        break;
+                    case 1:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf8, validUtf8));
+                        break;
+                    case 2:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(validUtf8, invalidUtf8));
+                        break;
+                    case 3:
+                        jsonUtf8.WriteString(validUtf8, validUtf8);
+                        break;
                 }
-                catch (ArgumentException) { }
             }
             jsonUtf8.WriteEndObject();
             jsonUtf8.Flush();
+        }
 
-            output.Dispose();
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidUTF16(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            var validUtf16 = new char[2] { (char)0xD801, (char)0xDC37 }; // 0x10437
+            var invalidUtf16 = new char[2] { (char)0xD801, 'a' };
+
+            jsonUtf8.WriteStartObject();
+            for (int i = 0; i < 4; i++)
+            {
+                switch (i)
+                {
+                    case 0:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf16, invalidUtf16));
+                        break;
+                    case 1:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf16, validUtf16));
+                        break;
+                    case 2:
+                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(validUtf16, invalidUtf16));
+                        break;
+                    case 3:
+                        jsonUtf8.WriteString(validUtf16, validUtf16);
+                        break;
+                }
+            }
+            jsonUtf8.WriteEndObject();
+            jsonUtf8.Flush();
         }
 
         [Theory]
@@ -1753,25 +2124,21 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WriteCustomStrings(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            var output = new ArrayBufferWriter(10);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(10);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             jsonUtf8.WriteStartObject();
 
             for (int i = 0; i < 1_000; i++)
-                jsonUtf8.WriteString("message", "Hello, World!", escape: false);
+            {
+                jsonUtf8.WriteString("message", "Hello, World!");
+            }
 
             jsonUtf8.WriteEndObject();
             jsonUtf8.Flush();
 
-            ArraySegment<byte> arraySegment = output.Formatted;
-            string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-            Assert.Equal(GetCustomExpectedString(formatted), actualStr);
-
-            output.Dispose();
+            AssertContents(GetCustomExpectedString(formatted), output);
         }
 
         [Theory]
@@ -1783,11 +2150,10 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetStartEndExpectedString(prettyPrint: formatted);
 
-            var output = new ArrayBufferWriter(1024);
-
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             jsonUtf8.WriteStartArray();
             jsonUtf8.WriteStartObject();
@@ -1795,12 +2161,7 @@ namespace System.Text.Json.Tests
             jsonUtf8.WriteEndArray();
             jsonUtf8.Flush();
 
-            ArraySegment<byte> arraySegment = output.Formatted;
-            string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-            Assert.Equal(expectedStr, actualStr);
-
-            output.Dispose();
+            AssertContents(expectedStr, output);
         }
 
         [Theory]
@@ -1811,43 +2172,31 @@ namespace System.Text.Json.Tests
             {
                 string expectedStr = "[}";
 
-                var output = new ArrayBufferWriter(1024);
+                var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true };
+                var output = new ArrayBufferWriter<byte>(1024);
 
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
-
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartArray();
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
 
             {
                 string expectedStr = "{]";
 
-                var output = new ArrayBufferWriter(1024);
-
-                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
+                var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true };
+                var output = new ArrayBufferWriter<byte>(1024);
 
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
                 jsonUtf8.WriteEndArray();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1860,34 +2209,25 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetStartEndWithPropertyArrayExpectedString(prettyPrint: formatted);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteStartArray("property name", escape: false);
+                        jsonUtf8.WriteStartArray("property name");
                         break;
                     case 1:
-                        jsonUtf8.WriteStartArray("property name".AsSpan(), escape: false);
+                        jsonUtf8.WriteStartArray("property name".AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteStartArray("property name", escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteStartArray("property name".AsSpan(), escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: true);
+                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"));
                         break;
                 }
 
@@ -1895,12 +2235,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1923,36 +2258,26 @@ namespace System.Text.Json.Tests
             var key = new string(keyChars);
 
             string expectedStr = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: true);
-            string expectedStrNoEscape = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteStartArray(key, escape: false);
+                        jsonUtf8.WriteStartArray(key);
                         break;
                     case 1:
-                        jsonUtf8.WriteStartArray(key.AsSpan(), escape: false);
+                        jsonUtf8.WriteStartArray(key.AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteStartArray(key, escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteStartArray(key.AsSpan(), escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: true);
+                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key));
                         break;
                 }
 
@@ -1960,15 +2285,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.Equal(expectedStrNoEscape, actualStr);
-                else
-                    Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -1981,34 +2298,25 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetStartEndWithPropertyObjectExpectedString(prettyPrint: formatted);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteStartObject("property name", escape: false);
+                        jsonUtf8.WriteStartObject("property name");
                         break;
                     case 1:
-                        jsonUtf8.WriteStartObject("property name".AsSpan(), escape: false);
+                        jsonUtf8.WriteStartObject("property name".AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteStartObject("property name", escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteStartObject("property name".AsSpan(), escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: true);
+                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"));
                         break;
                 }
 
@@ -2016,12 +2324,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2044,52 +2347,34 @@ namespace System.Text.Json.Tests
             var key = new string(keyChars);
 
             string expectedStr = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: true);
-            string expectedStrNoEscape = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteStartObject(key, escape: false);
+                        jsonUtf8.WriteStartObject(key);
                         break;
                     case 1:
-                        jsonUtf8.WriteStartObject(key.AsSpan(), escape: false);
+                        jsonUtf8.WriteStartObject(key.AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteStartObject(key, escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteStartObject(key.AsSpan(), escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: true);
+                        jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key));
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
-                jsonUtf8.WriteEndObject();
-                jsonUtf8.Flush();
-
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.Equal(expectedStrNoEscape, actualStr);
-                else
-                    Assert.Equal(expectedStr, actualStr);
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush();
 
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2102,25 +2387,25 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetArrayWithPropertyExpectedString(prettyPrint: formatted);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
+                var output = new ArrayBufferWriter<byte>(1024);
 
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteStartArray("message", escape: false);
+                        jsonUtf8.WriteStartArray("message");
                         break;
                     case 1:
-                        jsonUtf8.WriteStartArray("message".AsSpan(), escape: false);
+                        jsonUtf8.WriteStartArray("message".AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message"), escape: false);
+                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message"));
                         break;
                 }
 
@@ -2128,12 +2413,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2165,36 +2445,26 @@ namespace System.Text.Json.Tests
         public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, string keyString)
         {
             string expectedStr = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: true);
-            string expectedStrNoEscape = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteBoolean(keyString, value, escape: false);
+                        jsonUtf8.WriteBoolean(keyString, value);
                         break;
                     case 1:
-                        jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: false);
+                        jsonUtf8.WriteBoolean(keyString.AsSpan(), value);
                         break;
                     case 2:
-                        jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteBoolean(keyString, value, escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: true);
+                        jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value);
                         break;
                 }
 
@@ -2208,15 +2478,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}, | Value: {value}");
-                else
-                    Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2236,36 +2498,29 @@ namespace System.Text.Json.Tests
         public void WriteNullValue(bool formatted, bool skipValidation, string keyString)
         {
             string expectedStr = GetNullExpectedString(prettyPrint: formatted, keyString, escape: true);
-            string expectedStrNoEscape = GetNullExpectedString(prettyPrint: formatted, keyString, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(16);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteNull(keyString, escape: false);
+                        jsonUtf8.WriteNull(keyString);
+                        jsonUtf8.WriteNull(keyString);
                         break;
                     case 1:
-                        jsonUtf8.WriteNull(keyString.AsSpan(), escape: false);
+                        jsonUtf8.WriteNull(keyString.AsSpan());
+                        jsonUtf8.WriteNull(keyString.AsSpan());
                         break;
                     case 2:
-                        jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: false);
-                        break;
-                    case 3:
-                        jsonUtf8.WriteNull(keyString, escape: true);
-                        break;
-                    case 4:
-                        jsonUtf8.WriteNull(keyString.AsSpan(), escape: true);
-                        break;
-                    case 5:
-                        jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: true);
+                        jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString));
+                        jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString));
                         break;
                 }
 
@@ -2277,15 +2532,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}");
-                else
-                    Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2318,37 +2565,32 @@ namespace System.Text.Json.Tests
         {
             string expectedStr = GetIntegerExpectedString(prettyPrint: formatted, value);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
                 switch (i)
                 {
                     case 0:
-                        jsonUtf8.WriteNumber("message", value, escape: false);
+                        jsonUtf8.WriteNumber("message", value);
                         break;
                     case 1:
-                        jsonUtf8.WriteNumber("message".AsSpan(), value, escape: false);
+                        jsonUtf8.WriteNumber("message".AsSpan(), value);
                         break;
                     case 2:
-                        jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value, escape: false);
+                        jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value);
                         break;
                 }
 
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}");
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2493,14 +2735,13 @@ namespace System.Text.Json.Tests
             }
 
             string expectedStr = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false);
-            string expectedStrNoEscape = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
-            for (int j = 0; j < 6; j++)
+            for (int j = 0; j < 3; j++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 ReadOnlySpan<char> keyUtf16 = keyString.AsSpan();
                 ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
@@ -2511,105 +2752,54 @@ namespace System.Text.Json.Tests
                 {
                     case 0:
                         for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, floats[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, floats[i]);
                         for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, ints[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, ints[i]);
                         for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, uints[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, uints[i]);
                         for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, doubles[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, doubles[i]);
                         for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, longs[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, longs[i]);
                         for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, ulongs[i], escape: false);
+                            jsonUtf8.WriteNumber(keyString, ulongs[i]);
                         for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, decimals[i], escape: false);
-                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                            jsonUtf8.WriteNumber(keyString, decimals[i]);
+                        jsonUtf8.WriteStartArray(keyString);
                         break;
                     case 1:
                         for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, floats[i]);
                         for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, ints[i]);
                         for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, uints[i]);
                         for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, doubles[i]);
                         for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, longs[i]);
                         for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, ulongs[i]);
                         for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                            jsonUtf8.WriteNumber(keyUtf16, decimals[i]);
+                        jsonUtf8.WriteStartArray(keyUtf16);
                         break;
                     case 2:
                         for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: false);
-                        for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: false);
-                        for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: false);
-                        for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: false);
-                        for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: false);
-                        for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: false);
-                        for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: false);
-                        break;
-                    case 3:
-                        for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, floats[i], escape: true);
-                        for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, ints[i], escape: true);
-                        for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, uints[i], escape: true);
-                        for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, doubles[i], escape: true);
-                        for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, longs[i], escape: true);
-                        for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, ulongs[i], escape: true);
-                        for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyString, decimals[i], escape: true);
-                        jsonUtf8.WriteStartArray(keyString, escape: true);
-                        break;
-                    case 4:
-                        for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: true);
-                        for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: true);
-                        for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: true);
-                        for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: true);
-                        for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: true);
-                        for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: true);
-                        for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: true);
-                        break;
-                    case 5:
-                        for (int i = 0; i < floats.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, floats[i]);
                         for (int i = 0; i < ints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, ints[i]);
                         for (int i = 0; i < uints.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, uints[i]);
                         for (int i = 0; i < doubles.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, doubles[i]);
                         for (int i = 0; i < longs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, longs[i]);
                         for (int i = 0; i < ulongs.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, ulongs[i]);
                         for (int i = 0; i < decimals.Length; i++)
-                            jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: true);
+                            jsonUtf8.WriteNumber(keyUtf8, decimals[i]);
+                        jsonUtf8.WriteStartArray(keyUtf8);
                         break;
                 }
 
@@ -2625,17 +2815,205 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
                 // TODO: The output doesn't match what JSON.NET does (different rounding/e-notation).
-                //if (j < 3)
-                //    Assert.Equal(expectedStrNoEscape, actualStr);
-                //else
-                //    Assert.Equal(expectedStr, actualStr);
+                // AssertContents(expectedStr, output);
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueInt32(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue(1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueInt64(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((long)1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueUInt32(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((uint)1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueUInt64(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((ulong)1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueSingle(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
-                output.Dispose();
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((float)1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueDouble(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((double)1234567);
+                numberOfElements++;
+            }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteNumberValueDecimal(bool formatted, bool skipValidation)
+        {
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+            var output = new ArrayBufferWriter<byte>();
+            int initialCapacity = output.Capacity;
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+            int numberOfElements = 0;
+            jsonUtf8.WriteStartArray();
+            while (initialCapacity == output.Capacity)
+            {
+                jsonUtf8.WriteNumberValue((decimal)1234567);
+                numberOfElements++;
             }
+            Assert.Equal(initialCapacity + 4096, output.Capacity);
+            jsonUtf8.WriteEndArray();
+            jsonUtf8.Flush();
+
+            string expectedStr = GetNumbersExpectedString(formatted, numberOfElements);
+            AssertContents(expectedStr, output);
         }
 
         [Theory]
@@ -2657,20 +3035,21 @@ namespace System.Text.Json.Tests
 
             var guids = new Guid[numberOfItems];
             for (int i = 0; i < numberOfItems; i++)
+            {
                 guids[i] = Guid.NewGuid();
+            }
 
             string expectedStr = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: true);
-            string expectedStrNoEscape = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             ReadOnlySpan<char> keyUtf16 = keyString.AsSpan();
             ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
@@ -2678,33 +3057,18 @@ namespace System.Text.Json.Tests
                 {
                     case 0:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, guids[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                            jsonUtf8.WriteString(keyString, guids[j]);
+                        jsonUtf8.WriteStartArray(keyString);
                         break;
                     case 1:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, guids[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                            jsonUtf8.WriteString(keyUtf16, guids[j]);
+                        jsonUtf8.WriteStartArray(keyUtf16);
                         break;
                     case 2:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, guids[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: false);
-                        break;
-                    case 3:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, guids[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyString, escape: true);
-                        break;
-                    case 4:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, guids[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: true);
-                        break;
-                    case 5:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, guids[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: true);
+                            jsonUtf8.WriteString(keyUtf8, guids[j]);
+                        jsonUtf8.WriteStartArray(keyUtf8);
                         break;
                 }
 
@@ -2715,15 +3079,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.Equal(expectedStrNoEscape, actualStr);
-                else
-                    Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2753,17 +3109,16 @@ namespace System.Text.Json.Tests
                 dates[i] = start.AddDays(random.Next(range));
 
             string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true);
-            string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             ReadOnlySpan<char> keyUtf16 = keyString.AsSpan();
             ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
@@ -2771,33 +3126,18 @@ namespace System.Text.Json.Tests
                 {
                     case 0:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                            jsonUtf8.WriteString(keyString, dates[j]);
+                        jsonUtf8.WriteStartArray(keyString);
                         break;
                     case 1:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                            jsonUtf8.WriteString(keyUtf16, dates[j]);
+                        jsonUtf8.WriteStartArray(keyUtf16);
                         break;
                     case 2:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: false);
-                        break;
-                    case 3:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyString, escape: true);
-                        break;
-                    case 4:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: true);
-                        break;
-                    case 5:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: true);
+                            jsonUtf8.WriteString(keyUtf8, dates[j]);
+                        jsonUtf8.WriteStartArray(keyUtf8);
                         break;
                 }
 
@@ -2808,15 +3148,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.Equal(expectedStrNoEscape, actualStr);
-                else
-                    Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2846,17 +3178,16 @@ namespace System.Text.Json.Tests
                 dates[i] = new DateTimeOffset(start.AddDays(random.Next(range)));
 
             string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true);
-            string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false);
 
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             ReadOnlySpan<char> keyUtf16 = keyString.AsSpan();
             ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
 
-            for (int i = 0; i < 6; i++)
+            for (int i = 0; i < 3; i++)
             {
-                var output = new ArrayBufferWriter(1024);
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
 
                 jsonUtf8.WriteStartObject();
 
@@ -2864,33 +3195,18 @@ namespace System.Text.Json.Tests
                 {
                     case 0:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                            jsonUtf8.WriteString(keyString, dates[j]);
+                        jsonUtf8.WriteStartArray(keyString);
                         break;
                     case 1:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                            jsonUtf8.WriteString(keyUtf16, dates[j]);
+                        jsonUtf8.WriteStartArray(keyUtf16);
                         break;
                     case 2:
                         for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, dates[j], escape: false);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: false);
-                        break;
-                    case 3:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyString, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyString, escape: true);
-                        break;
-                    case 4:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf16, escape: true);
-                        break;
-                    case 5:
-                        for (int j = 0; j < numberOfItems; j++)
-                            jsonUtf8.WriteString(keyUtf8, dates[j], escape: true);
-                        jsonUtf8.WriteStartArray(keyUtf8, escape: true);
+                            jsonUtf8.WriteString(keyUtf8, dates[j]);
+                        jsonUtf8.WriteStartArray(keyUtf8);
                         break;
                 }
 
@@ -2901,15 +3217,7 @@ namespace System.Text.Json.Tests
                 jsonUtf8.WriteEndObject();
                 jsonUtf8.Flush();
 
-                ArraySegment<byte> arraySegment = output.Formatted;
-                string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
-
-                if (i < 3)
-                    Assert.Equal(expectedStrNoEscape, actualStr);
-                else
-                    Assert.Equal(expectedStr, actualStr);
-
-                output.Dispose();
+                AssertContents(expectedStr, output);
             }
         }
 
@@ -2921,10 +3229,8 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WriteLargeKeyOrValue(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
-
-            Span<byte> key;
-            Span<byte> value;
+            byte[] key;
+            byte[] value;
 
             try
             {
@@ -2936,30 +3242,25 @@ namespace System.Text.Json.Tests
                 return;
             }
 
-            key.Fill((byte)'a');
-            value.Fill((byte)'b');
+            key.AsSpan().Fill((byte)'a');
+            value.AsSpan().Fill((byte)'b');
 
-            var output = new ArrayBufferWriter(1024);
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+            var output = new ArrayBufferWriter<byte>(1024);
 
-            try
             {
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteStartObject();
-                jsonUtf8.WriteString(key, DateTime.Now, escape: false);
-                Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. KeyLength: {key.Length}");
+                Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(key, DateTime.Now));
+                Assert.Equal(0, output.WrittenCount);
             }
-            catch (ArgumentException) { }
 
-            try
             {
-                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
                 jsonUtf8.WriteStartArray();
-                jsonUtf8.WriteStringValue(value, escape: false);
-                Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. ValueLength: {value.Length}");
+                Assert.Throws<ArgumentException>(() => jsonUtf8.WriteStringValue(value));
+                Assert.Equal(0, output.WrittenCount);
             }
-            catch (ArgumentException) { }
-
-            output.Dispose();
         }
 
         [ConditionalTheory(nameof(IsX64))]
@@ -2970,7 +3271,7 @@ namespace System.Text.Json.Tests
         [InlineData(false, false)]
         public void WriteLargeKeyValue(bool formatted, bool skipValidation)
         {
-            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
 
             Span<byte> key;
             Span<byte> value;
@@ -2985,23 +3286,23 @@ namespace System.Text.Json.Tests
                 return;
             }
 
-            WriteTooLargeHelper(state, key, value);
-            WriteTooLargeHelper(state, key.Slice(0, 1_000_000_000), value);
-            WriteTooLargeHelper(state, key, value.Slice(0, 1_000_000_000));
-            WriteTooLargeHelper(state, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true);
+            WriteTooLargeHelper(options, key, value);
+            WriteTooLargeHelper(options, key.Slice(0, 1_000_000_000), value);
+            WriteTooLargeHelper(options, key, value.Slice(0, 1_000_000_000));
+            WriteTooLargeHelper(options, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true);
         }
 
-        private static void WriteTooLargeHelper(JsonWriterState state, ReadOnlySpan<byte> key, ReadOnlySpan<byte> value, bool noThrow = false)
+        private static void WriteTooLargeHelper(JsonWriterOptions options, ReadOnlySpan<byte> key, ReadOnlySpan<byte> value, bool noThrow = false)
         {
             // Resizing is too slow, even for outerloop tests, so initialize to a large output size up front.
-            var output = new ArrayBufferWriter(noThrow ? 40_000_000 : 1024);
-            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            var output = new ArrayBufferWriter<byte>(noThrow ? 40_000_000 : 1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, options);
 
             jsonUtf8.WriteStartObject();
 
             try
             {
-                jsonUtf8.WriteString(key, value, escape: false);
+                jsonUtf8.WriteString(key, value);
 
                 if (!noThrow)
                 {
@@ -3018,47 +3319,24 @@ namespace System.Text.Json.Tests
 
             jsonUtf8.WriteEndObject();
             jsonUtf8.Flush();
-
-            output.Dispose();
-        }
-
-        private static void WriterDidNotThrow(bool skipValidation)
-        {
-            if (skipValidation)
-                Assert.True(true, "Did not expect InvalidOperationException to be thrown since validation was skipped.");
-            else
-                Assert.True(false, "Expected InvalidOperationException to be thrown when validation is enabled.");
-        }
-
-        private static void WriterDidNotThrow(bool skipValidation, string message)
-        {
-            if (skipValidation)
-                Assert.True(true, message);
-            else
-                Assert.True(false, message);
-        }
-
-        private static void AssertWriterThrow(bool noThrow)
-        {
-            if (noThrow)
-                Assert.True(true, "Did not expect InvalidOperationException to be thrown since input was valid (or suppressEscaping was true).");
-            else
-                Assert.True(false, "Expected InvalidOperationException to be thrown when user passes invalid UTF-8.");
         }
 
-        private static string GetHelloWorldExpectedString(bool prettyPrint)
+        private static string GetHelloWorldExpectedString(bool prettyPrint, string propertyName, string value)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
             {
-                Formatting = prettyPrint ? Formatting.Indented : Formatting.None
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
+                StringEscapeHandling = StringEscapeHandling.EscapeHtml
             };
 
             json.WriteStartObject();
-            json.WritePropertyName("message");
-            json.WriteValue("Hello, World!");
+            json.WritePropertyName(propertyName);
+            json.WriteValue(value);
+            json.WritePropertyName(propertyName);
+            json.WriteValue(value);
             json.WriteEnd();
 
             json.Flush();
@@ -3068,7 +3346,7 @@ namespace System.Text.Json.Tests
 
         private static string GetCommentExpectedString(bool prettyPrint, string comment)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3091,7 +3369,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStringsExpectedString(bool prettyPrint, string value)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3111,7 +3389,7 @@ namespace System.Text.Json.Tests
 
         private static string GetEscapedExpectedString(bool prettyPrint, string propertyName, string value, StringEscapeHandling escaping, bool escape = true)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3132,7 +3410,7 @@ namespace System.Text.Json.Tests
 
         private static string GetCustomExpectedString(bool prettyPrint)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3155,7 +3433,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStartEndExpectedString(bool prettyPrint)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3175,7 +3453,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStartEndWithPropertyArrayExpectedString(bool prettyPrint)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3196,7 +3474,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStartEndWithPropertyArrayExpectedString(string key, bool prettyPrint, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3218,7 +3496,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStartEndWithPropertyObjectExpectedString(bool prettyPrint)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3239,7 +3517,7 @@ namespace System.Text.Json.Tests
 
         private static string GetStartEndWithPropertyObjectExpectedString(string key, bool prettyPrint, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3261,7 +3539,7 @@ namespace System.Text.Json.Tests
 
         private static string GetArrayWithPropertyExpectedString(bool prettyPrint)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3281,7 +3559,7 @@ namespace System.Text.Json.Tests
 
         private static string GetBooleanExpectedString(bool prettyPrint, string keyString, bool value, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3311,7 +3589,7 @@ namespace System.Text.Json.Tests
 
         private static string GetNullExpectedString(bool prettyPrint, string keyString, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3323,6 +3601,8 @@ namespace System.Text.Json.Tests
             json.WriteStartObject();
             json.WritePropertyName(keyString, escape);
             json.WriteNull();
+            json.WritePropertyName(keyString, escape);
+            json.WriteNull();
 
             json.WritePropertyName("temp");
             json.WriteStartArray();
@@ -3339,7 +3619,7 @@ namespace System.Text.Json.Tests
 
         private static string GetIntegerExpectedString(bool prettyPrint, int value)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3359,7 +3639,7 @@ namespace System.Text.Json.Tests
 
         private static string GetNumbersExpectedString(bool prettyPrint, string keyString, int[] ints, uint[] uints, long[] longs, ulong[] ulongs, float[] floats, double[] doubles, decimal[] decimals, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3425,7 +3705,7 @@ namespace System.Text.Json.Tests
 
         private static string GetGuidsExpectedString(bool prettyPrint, string keyString, Guid[] guids, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3455,9 +3735,31 @@ namespace System.Text.Json.Tests
             return Encoding.UTF8.GetString(ms.ToArray());
         }
 
+        private static string GetNumbersExpectedString(bool prettyPrint, int numberOfElements)
+        {
+            var ms = new MemoryStream();
+            TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+            var json = new JsonTextWriter(streamWriter)
+            {
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
+            };
+
+            json.WriteStartArray();
+            for (int i = 0; i < numberOfElements; i++)
+            {
+                json.WriteValue(1234567);
+            }
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
         private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTime[] dates, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3489,7 +3791,7 @@ namespace System.Text.Json.Tests
 
         private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTimeOffset[] dates, bool escape = false)
         {
-            MemoryStream ms = new MemoryStream();
+            var ms = new MemoryStream();
             TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
 
             var json = new JsonTextWriter(streamWriter)
@@ -3518,5 +3820,17 @@ namespace System.Text.Json.Tests
 
             return Encoding.UTF8.GetString(ms.ToArray());
         }
+
+        private static void AssertContents(string expectedValue, ArrayBufferWriter<byte> buffer)
+        {
+            Assert.Equal(
+                expectedValue,
+                Encoding.UTF8.GetString(
+                    buffer.WrittenSpan
+#if netstandard
+                        .ToArray()
+#endif
+                    ));
+        }
     }
 }