Add Utf8JsonWriter along with unit tests (dotnet/corefx#34425)
authorAhson Khan <ahkha@microsoft.com>
Tue, 15 Jan 2019 22:42:39 +0000 (14:42 -0800)
committerGitHub <noreply@github.com>
Tue, 15 Jan 2019 22:42:39 +0000 (14:42 -0800)
* Move JsonReader related files to the Reader directory.

* Update S.T.Json ref to include new JsonWriter APIs.

* Port initial JsonWriter files from corefxlab.

* Auto-generate System.Text.Json ref to update the ordering for
consistency.

* Fixed some TODOs, delete dead code, and formatting cleanup.

* Make use of self-descriptive constants where possible.

* Fix leftover TODOs and update throw helper/exception messages.

* Add xml comments/docs to the public surface area.

* Add JsonWriter unit tests.

* Change GetCurrentState back to a property and explicitly Flush on the
callers behalf instead of throwing.

* Save current depth as part of state and update tests.

* Fix constant names, account for quotes in bytes needed, and add fixed buffer writer tests.

* Fix inconsistent use of braces by adding them for single line ifs

* Update parameter name to exclude encoding.

* Remove JsonWriterException and use InvalidOperationException instead.

* Use Rune and Utf8Formatter/TryFormat in more places and remove
UnicodeScalar.

* Fix nits, typos, and reorder field assignment and method calls.

* Pass spans by in (or by value) instead of by ref.

* Update comments and remove unnecessary test.

* Remove some aggressive inlining and pass spans by value rather than in

* Update comments, dont compute bytes needed, and use if instead of loop before advancing.

* Reduce code bloat by removing duplciate calls to ValidateX.

* Add details on how .NET types are formatted to comments.

* Reduce code duplication when writing values.

* Change the StandardFormat used for DateTime(Offset) to 'O'

* Refactor calculating the maximum escaped length to a helper.

* Remove unnecessary checks and rename locals to be more descriptive.

* Rename suppressEscaping to escape and flip default from false to true.

* Comment cleanup, add debug.asserts, and move transcoding helpers to a
separate file.

* Increase the deicmal max size to account for sign and add tests.

* Rename ROS<byte> property name and value params to include utf8 in the
name.

* Remove redundant code where idx is set to 0 unnecessarily.

* Remove dead code (dont escape forward slash) and make tests culture
invariant.

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

52 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
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/JsonConstants.cs
src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeNumberResult.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/ConsumeNumberResult.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeTokenResult.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/ConsumeTokenResult.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderException.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderException.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderHelper.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderOptions.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderState.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.MultiSegment.cs with 99% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.TryGet.cs with 100% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.cs with 99% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Transcoding.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/SequenceValidity.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/BitStackTests.cs
src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/InvalidBufferWriter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/ResizableArray.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs [new file with mode: 0644]

index b95392a..25aa94e 100644 (file)
@@ -49,6 +49,20 @@ namespace System.Text.Json
         String = (byte)6,
         True = (byte)8,
     }
+    public partial struct JsonWriterOptions
+    {
+        private object _dummy;
+        public bool Indented { get { throw null; } set { } }
+        public bool SkipValidation { get { throw null; } set { } }
+    }
+    public partial struct JsonWriterState
+    {
+        private object _dummy;
+        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;
@@ -71,4 +85,95 @@ namespace System.Text.Json
         public bool TryGetInt64Value(out long value) { throw null; }
         public bool TryGetSingleValue(out float value) { throw null; }
     }
+    public ref partial struct Utf8JsonWriter
+    {
+        private object _dummy;
+        public Utf8JsonWriter(System.Buffers.IBufferWriter<byte> bufferWriter, System.Text.Json.JsonWriterState state = default(System.Text.Json.JsonWriterState)) { throw null; }
+        public long BytesCommitted { get { throw null; } }
+        public long BytesWritten { 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 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 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 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) { }
+        [System.CLSCompliantAttribute(false)]
+        public void WriteNumber(System.ReadOnlySpan<byte> utf8PropertyName, uint value, bool escape = true) { }
+        [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) { }
+        [System.CLSCompliantAttribute(false)]
+        public void WriteNumber(System.ReadOnlySpan<char> propertyName, uint value, bool escape = true) { }
+        [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) { }
+        [System.CLSCompliantAttribute(false)]
+        public void WriteNumber(string propertyName, uint value, bool escape = true) { }
+        [System.CLSCompliantAttribute(false)]
+        public void WriteNumber(string propertyName, ulong value, bool escape = true) { }
+        public void WriteNumberValue(decimal value) { }
+        public void WriteNumberValue(double value) { }
+        public void WriteNumberValue(int value) { }
+        public void WriteNumberValue(long value) { }
+        public void WriteNumberValue(float value) { }
+        [System.CLSCompliantAttribute(false)]
+        public void WriteNumberValue(uint value) { }
+        [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 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 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) { }
+    }
 }
index c1c4779..33d1d0f 100644 (file)
   <data name="ArrayDepthTooLarge" xml:space="preserve">
     <value>CurrentDepth ({0}) is larger than the maximum configured depth of {1}. Cannot read next JSON array.</value>
   </data>
+  <data name="CallFlushToAvoidDataLoss" xml:space="preserve">
+    <value>The JSON writer needs to be flushed before getting the current state. There are {0} bytes that have not been committed to the output.</value>
+  </data>
+  <data name="CannotStartObjectArrayAfterPrimitiveOrClose" xml:space="preserve">
+    <value>Cannot write the start of an object/array after a single JSON value or outside of an existing closed object/array. Current token type is '{0}'.</value>
+  </data>
+  <data name="CannotStartObjectArrayWithoutProperty" xml:space="preserve">
+    <value>Cannot write the start of an object or array without a property name. Current token type is '{0}'.</value>
+  </data>
+  <data name="CannotWriteInvalidUTF16" xml:space="preserve">
+    <value>Cannot write invalid UTF-16 text as JSON. Invalid surrogate pair: '{0}'.</value>
+  </data>
+  <data name="CannotWriteInvalidUTF8" xml:space="preserve">
+    <value>Cannot write invalid UTF-8 text as JSON. Invalid input: '{0}'.</value>
+  </data>
+  <data name="CannotWritePropertyWithinArray" xml:space="preserve">
+    <value>Cannot write a JSON property within an array or as the first JSON token. Current token type is '{0}'.</value>
+  </data>
+  <data name="CannotWriteValueAfterPrimitive" xml:space="preserve">
+    <value>Cannot write a JSON value after a single JSON value. Current token type is '{0}'.</value>
+  </data>
+  <data name="CannotWriteValueWithinObject" xml:space="preserve">
+    <value>Cannot write a JSON value within an object without a property name. Current token type is '{0}'.</value>
+  </data>
+  <data name="DepthTooLarge" xml:space="preserve">
+    <value>CurrentDepth ({0}) is equal to or larger than the maximum allowed depth of {1}. Cannot write the next JSON object or array.</value>
+  </data>
+  <data name="EmptyJsonIsInvalid" xml:space="preserve">
+    <value>Writing an empty JSON payload (excluding comments) is invalid.</value>
+  </data>
   <data name="EndOfCommentNotFound" xml:space="preserve">
     <value>Expected end of comment, but instead reached end of data.</value>
   </data>
   <data name="ExpectedValueAfterPropertyNameNotFound" xml:space="preserve">
     <value>Expected a value, but instead reached end of data.</value>
   </data>
+  <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="ObjectDepthTooLarge" xml:space="preserve">
     <value>CurrentDepth ({0}) is larger than the maximum configured depth of {1}. Cannot read next JSON object.</value>
   </data>
+  <data name="PropertyNameTooLarge" xml:space="preserve">
+    <value>The JSON property name of length {0} is too large and not supported by the JSON writer.</value>
+  </data>
   <data name="RequiredDigitNotFoundAfterDecimal" xml:space="preserve">
     <value>'{0}' is invalid within a number, immediately after a decimal point ('.'). Expected a digit ('0'-'9').</value>
   </data>
   <data name="RequiredDigitNotFoundEndOfData" xml:space="preserve">
     <value>Expected a digit ('0'-'9'), but instead reached end of data.</value>
   </data>
+  <data name="SpecialNumberValuesNotSupported" xml:space="preserve">
+    <value>.NET number values such as positive and negative infinity cannot be written as valid JSON.</value>
+  </data>
+  <data name="ValueTooLarge" xml:space="preserve">
+    <value>The JSON value of length {0} is too large and not supported by the JSON writer.</value>
+  </data>
   <data name="ZeroDepthAtEnd" xml:space="preserve">
     <value>Expected CurrentDepth ({0}) to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed.</value>
   </data>
index a158dab..6d774c2 100644 (file)
@@ -8,19 +8,49 @@
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="System\Text\Json\BitStack.cs" />
-    <Compile Include="System\Text\Json\ConsumeNumberResult.cs" />
-    <Compile Include="System\Text\Json\ConsumeTokenResult.cs" />
     <Compile Include="System\Text\Json\JsonCommentHandling.cs" />
     <Compile Include="System\Text\Json\JsonConstants.cs" />
-    <Compile Include="System\Text\Json\JsonReaderException.cs" />
-    <Compile Include="System\Text\Json\JsonReaderHelper.cs" />
-    <Compile Include="System\Text\Json\JsonReaderOptions.cs" />
-    <Compile Include="System\Text\Json\JsonReaderState.cs" />
     <Compile Include="System\Text\Json\JsonTokenType.cs" />
     <Compile Include="System\Text\Json\ThrowHelper.cs" />
-    <Compile Include="System\Text\Json\Utf8JsonReader.cs" />
-    <Compile Include="System\Text\Json\Utf8JsonReader.MultiSegment.cs" />
-    <Compile Include="System\Text\Json\Utf8JsonReader.TryGet.cs" />
+    <Compile Include="System\Text\Json\Reader\ConsumeNumberResult.cs" />
+    <Compile Include="System\Text\Json\Reader\ConsumeTokenResult.cs" />
+    <Compile Include="System\Text\Json\Reader\JsonReaderException.cs" />
+    <Compile Include="System\Text\Json\Reader\JsonReaderHelper.cs" />
+    <Compile Include="System\Text\Json\Reader\JsonReaderOptions.cs" />
+    <Compile Include="System\Text\Json\Reader\JsonReaderState.cs" />
+    <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\Writer\JsonWriterHelper.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.WriteProperties.DateTimeOffset.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Decimal.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Double.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Float.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Guid.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Helpers.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Literal.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.SignedNumber.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.String.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.UnsignedNumber.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Comment.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.DateTime.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.DateTimeOffset.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Decimal.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Double.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Float.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Guid.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Helpers.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Literal.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.SignedNumber.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.String.cs" />
+    <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.UnsignedNumber.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\System.Collections\src\System.Collections.csproj" />
@@ -30,6 +60,7 @@
     <ProjectReference Include="..\..\System.Numerics.Vectors\src\System.Numerics.Vectors.csproj" />
     <ProjectReference Include="..\..\System.Resources.ResourceManager\src\System.Resources.ResourceManager.csproj" />
     <ProjectReference Include="..\..\System.Runtime\src\System.Runtime.csproj" />
+    <ProjectReference Include="..\..\System.Runtime.Extensions\src\System.Runtime.Extensions.csproj" />
     <ProjectReference Include="..\..\System.Text.Encoding.Extensions\src\System.Text.Encoding.Extensions.csproj" />
   </ItemGroup>
   <ItemGroup>
index 859d706..c56f8d9 100644 (file)
@@ -14,7 +14,7 @@ namespace System.Text.Json
         public const byte CarriageReturn = (byte)'\r';
         public const byte LineFeed = (byte)'\n';
         public const byte Tab = (byte)'\t';
-        public const byte ListSeperator = (byte)',';
+        public const byte ListSeparator = (byte)',';
         public const byte KeyValueSeperator = (byte)':';
         public const byte Quote = (byte)'"';
         public const byte BackSlash = (byte)'\\';
@@ -28,13 +28,44 @@ namespace System.Text.Json
         public static ReadOnlySpan<byte> NullValue => new byte[] { (byte)'n', (byte)'u', (byte)'l', (byte)'l' };
 
         // Used to search for the end of a number
-        public static ReadOnlySpan<byte> Delimiters => new byte[] { ListSeperator, CloseBrace, CloseBracket, Space, LineFeed, CarriageReturn, Tab, Slash };
-
-        public static ReadOnlySpan<byte> WhiteSpace => new byte[] { Space, LineFeed, CarriageReturn, Tab };
-
-        public static ReadOnlySpan<byte> EndOfComment => new byte[] { Asterisk, Slash };
+        public static ReadOnlySpan<byte> Delimiters => new byte[] { ListSeparator, CloseBrace, CloseBracket, Space, LineFeed, CarriageReturn, Tab, Slash };
 
         // Explicitly skipping ReverseSolidus since that is handled separately
         public static ReadOnlySpan<byte> EscapableChars => new byte[] { Quote, (byte)'n', (byte)'r', (byte)'t', Slash, (byte)'u', (byte)'b', (byte)'f' };
+
+        public const int SpacesPerIndent = 2;
+        public const int MaxWriterDepth = 1_000;
+        public const int RemoveFlagsBitMask = 0x7FFFFFFF;
+
+        // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped.
+        // For example: '+' becomes '\u0043'
+        // Escaping surrogate pairs (represented by 3 or 4 utf-8 bytes) would expand to 12 bytes (which is still <= 6x).
+        // The same factor applies to utf-16 characters.
+        public const int MaxExpansionFactorWhileEscaping = 6;
+
+        public const int MaxTokenSize = 2_000_000_000 / MaxExpansionFactorWhileEscaping;  // 357_913_941 bytes
+        public const int MaxCharacterTokenSize = 2_000_000_000 / MaxExpansionFactorWhileEscaping; // 357_913_941 characters
+
+        public const int MaximumFormatInt64Length = 20;   // 19 + sign (i.e. -9223372036854775808)
+        public const int MaximumFormatUInt64Length = 20;  // i.e. 18446744073709551615
+        public const int MaximumFormatDoubleLength = 128;  // default (i.e. 'G'), using 128 (rather than say 32) to be future-proof.
+        public const int MaximumFormatSingleLength = 128;  // default (i.e. 'G'), using 128 (rather than say 32) to be future-proof.
+        public const int MaximumFormatDecimalLength = 31; // default (i.e. 'G')
+        public const int MaximumFormatGuidLength = 36;    // default (i.e. 'D'), 8 + 4 + 4 + 4 + 12 + 4 for the hyphens (e.g. 094ffa0a-0442-494d-b452-04003fa755cc)
+        public const int MaximumFormatDateTimeLength = 27;    // StandardFormat 'O', e.g. 2017-06-12T05:30:45.7680000
+        public const int MaximumFormatDateTimeOffsetLength = 33;  // StandardFormat 'O', e.g. 2017-06-12T05:30:45.7680000-07:00
+
+        // Encoding Helpers
+        public const char HighSurrogateStart = '\ud800';
+        public const char HighSurrogateEnd = '\udbff';
+        public const char LowSurrogateStart = '\udc00';
+        public const char LowSurrogateEnd = '\udfff';
+
+        public const int UnicodePlane01StartValue = 0x10000;
+        public const int HighSurrogateStartValue = 0xD800;
+        public const int HighSurrogateEndValue = 0xDBFF;
+        public const int LowSurrogateStartValue = 0xDC00;
+        public const int LowSurrogateEndValue = 0xDFFF;
+        public const int ShiftRightBy10 = 0x400;
     }
 }
@@ -1560,7 +1560,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
             }
 
-            if (marker == JsonConstants.ListSeperator)
+            if (marker == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
@@ -1669,7 +1669,7 @@ namespace System.Text.Json
 
             Debug.Assert(first != JsonConstants.Slash);
 
-            if (first == JsonConstants.ListSeperator)
+            if (first == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
@@ -1954,7 +1954,7 @@ namespace System.Text.Json
             {
                 ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
             }
-            else if (marker == JsonConstants.ListSeperator)
+            else if (marker == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
@@ -1221,7 +1221,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
             }
 
-            if (marker == JsonConstants.ListSeperator)
+            if (marker == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
@@ -1321,7 +1321,7 @@ namespace System.Text.Json
 
             Debug.Assert(first != JsonConstants.Slash);
 
-            if (first == JsonConstants.ListSeperator)
+            if (first == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
@@ -1593,7 +1593,7 @@ namespace System.Text.Json
             {
                 ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
             }
-            else if (marker == JsonConstants.ListSeperator)
+            else if (marker == JsonConstants.ListSeparator)
             {
                 _consumed++;
                 _bytePositionInLine++;
index 14e4927..4831b53 100644 (file)
@@ -20,6 +20,154 @@ namespace System.Text.Json
             return new ArgumentException(message);
         }
 
+        public static void ThrowArgumentException(string message)
+        {
+            throw GetArgumentException(message);
+        }
+
+        public static InvalidOperationException GetInvalidOperationException_CallFlushFirst(int _buffered)
+        {
+            return new InvalidOperationException(SR.Format(SR.CallFlushToAvoidDataLoss, _buffered));
+        }
+
+        public static void ThrowArgumentException_PropertyNameTooLarge(int tokenLength)
+        {
+            throw GetArgumentException(SR.Format(SR.PropertyNameTooLarge, tokenLength));
+        }
+
+        public static void ThrowArgumentException_ValueTooLarge(int tokenLength)
+        {
+            throw GetArgumentException(SR.Format(SR.ValueTooLarge, tokenLength));
+        }
+
+        public static void ThrowArgumentException_ValueNotSupported()
+        {
+            throw GetArgumentException(SR.SpecialNumberValuesNotSupported);
+        }
+
+        public static void ThrowArgumentException(ExceptionResource resource, int minimumSize = 0)
+        {
+            if (resource == ExceptionResource.FailedToGetLargerSpan)
+            {
+                throw GetArgumentException(SR.FailedToGetLargerSpan);
+            }
+            else
+            {
+                Debug.Assert(resource == ExceptionResource.FailedToGetMinimumSizeSpan);
+                throw GetArgumentException(SR.Format(SR.FailedToGetMinimumSizeSpan, minimumSize));
+            }
+        }
+
+        public static void ThrowArgumentException(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxTokenSize)
+            {
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+            else
+            {
+                Debug.Assert(value.Length > JsonConstants.MaxTokenSize);
+                ThrowArgumentException(SR.Format(SR.ValueTooLarge, value.Length));
+            }
+        }
+
+        public static void ThrowArgumentException(ReadOnlySpan<byte> propertyName, ReadOnlySpan<char> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxTokenSize)
+            {
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+            else
+            {
+                Debug.Assert(value.Length > JsonConstants.MaxCharacterTokenSize);
+                ThrowArgumentException(SR.Format(SR.ValueTooLarge, value.Length));
+            }
+        }
+
+        public static void ThrowArgumentException(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize)
+            {
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+            else
+            {
+                Debug.Assert(value.Length > JsonConstants.MaxTokenSize);
+                ThrowArgumentException(SR.Format(SR.ValueTooLarge, value.Length));
+            }
+        }
+
+        public static void ThrowArgumentException(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize)
+            {
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+            else
+            {
+                Debug.Assert(value.Length > JsonConstants.MaxCharacterTokenSize);
+                ThrowArgumentException(SR.Format(SR.ValueTooLarge, value.Length));
+            }
+        }
+
+        public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> propertyName, int currentDepth)
+        {
+            currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            if (currentDepth >= JsonConstants.MaxWriterDepth)
+            {
+                ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
+            }
+            else
+            {
+                Debug.Assert(propertyName.Length > JsonConstants.MaxCharacterTokenSize);
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+        }
+
+        public static void ThrowInvalidOperationException(string message)
+        {
+            throw GetInvalidOperationException(message);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static InvalidOperationException GetInvalidOperationException(string message)
+        {
+            return new InvalidOperationException(message);
+        }
+
+        public static void ThrowInvalidOperationException_DepthNonZeroOrEmptyJson(int currentDepth)
+        {
+            throw GetInvalidOperationException(currentDepth);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static InvalidOperationException GetInvalidOperationException(int currentDepth)
+        {
+            currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            if (currentDepth != 0)
+            {
+                return new InvalidOperationException(SR.Format(SR.ZeroDepthAtEnd, currentDepth));
+            }
+            else
+            {
+                return new InvalidOperationException(SR.Format(SR.EmptyJsonIsInvalid));
+            }
+        }
+
+        public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<char> propertyName, int currentDepth)
+        {
+            currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            if (currentDepth >= JsonConstants.MaxWriterDepth)
+            {
+                ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
+            }
+            else
+            {
+                Debug.Assert(propertyName.Length > JsonConstants.MaxCharacterTokenSize);
+                ThrowArgumentException(SR.Format(SR.PropertyNameTooLarge, propertyName.Length));
+            }
+        }
+
         public static InvalidOperationException GetInvalidOperationException_ExpectedNumber(JsonTokenType tokenType)
         {
             return GetInvalidOperationException("number", tokenType);
@@ -151,6 +299,86 @@ namespace System.Text.Json
 
             return message;
         }
+
+        public static void ThrowInvalidOperationException(ExceptionResource resource, int currentDepth = default, byte token = default, JsonTokenType tokenType = default)
+        {
+            throw GetInvalidOperationException(resource, currentDepth, token, tokenType);
+        }
+
+        public static void ThrowArgumentException_InvalidUTF8(ReadOnlySpan<byte> value)
+        {
+            var builder = new StringBuilder();
+
+            int printFirst10 = Math.Min(value.Length, 10);
+
+            for (int i = 0; i < printFirst10; i++)
+            {
+                byte nextByte = value[i];
+                if (IsPrintable(nextByte))
+                {
+                    builder.Append((char)nextByte);
+                }
+                else
+                {
+                    builder.Append($"0x{nextByte:X2}");
+                }
+            }
+
+            if (printFirst10 < value.Length)
+            {
+                builder.Append("...");
+            }
+
+            throw new ArgumentException(SR.Format(SR.CannotWriteInvalidUTF8, builder.ToString()));
+        }
+
+        public static void ThrowArgumentException_InvalidUTF16(int charAsInt)
+        {
+            throw new ArgumentException(SR.Format(SR.CannotWriteInvalidUTF16, $"0x{charAsInt:X2}"));
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static InvalidOperationException GetInvalidOperationException(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
+        {
+            string message = GetResourceString(resource, currentDepth, token, tokenType);
+            return new InvalidOperationException(message);
+        }
+
+        // This function will convert an ExceptionResource enum value to the resource string.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static string GetResourceString(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
+        {
+            string message = "";
+            switch (resource)
+            {
+                case ExceptionResource.MismatchedObjectArray:
+                    message = SR.Format(SR.MismatchedObjectArray, token);
+                    break;
+                case ExceptionResource.DepthTooLarge:
+                    message = SR.Format(SR.DepthTooLarge, currentDepth & JsonConstants.RemoveFlagsBitMask, JsonConstants.MaxWriterDepth);
+                    break;
+                case ExceptionResource.CannotStartObjectArrayWithoutProperty:
+                    message = SR.Format(SR.CannotStartObjectArrayWithoutProperty, tokenType);
+                    break;
+                case ExceptionResource.CannotStartObjectArrayAfterPrimitiveOrClose:
+                    message = SR.Format(SR.CannotStartObjectArrayAfterPrimitiveOrClose, tokenType);
+                    break;
+                case ExceptionResource.CannotWriteValueWithinObject:
+                    message = SR.Format(SR.CannotWriteValueWithinObject, tokenType);
+                    break;
+                case ExceptionResource.CannotWritePropertyWithinArray:
+                    message = SR.Format(SR.CannotWritePropertyWithinArray, tokenType);
+                    break;
+                case ExceptionResource.CannotWriteValueAfterPrimitive:
+                    message = SR.Format(SR.CannotWriteValueAfterPrimitive, tokenType);
+                    break;
+                default:
+                    Debug.Fail($"The ExceptionResource enum value: {resource} is not part of the switch. Add the appropriate case and exception message.");
+                    break;
+            }
+
+            return message;
+        }
     }
 
     internal enum ExceptionResource
@@ -180,5 +408,13 @@ namespace System.Text.Json
         MismatchedObjectArray,
         ObjectDepthTooLarge,
         ZeroDepthAtEnd,
+        DepthTooLarge,
+        CannotStartObjectArrayWithoutProperty,
+        CannotStartObjectArrayAfterPrimitiveOrClose,
+        CannotWriteValueWithinObject,
+        CannotWriteValueAfterPrimitive,
+        FailedToGetMinimumSizeSpan,
+        FailedToGetLargerSpan,
+        CannotWritePropertyWithinArray,
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs
new file mode 100644 (file)
index 0000000..18e9462
--- /dev/null
@@ -0,0 +1,475 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Text.Json
+{
+    // TODO: Replace the escaping logic with publicly shipping APIs from https://github.com/dotnet/corefx/issues/33509
+    internal static partial class JsonWriterHelper
+    {
+        // Only allow ASCII characters between ' ' (0x20) and '~' (0x7E), inclusively,
+        // but exclude characters that need to be escaped as hex: '"', '\'', '&', '+', '<', '>', '`'
+        // and exclude characters that need to be escaped by adding a backslash: '\n', '\r', '\t', '\\', '/', '\b', '\f'
+        //
+        // non-zero = allowed, 0 = disallowed
+        private static ReadOnlySpan<byte> AllowList => new byte[byte.MaxValue + 1] {
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
+            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        };
+
+        private static readonly char[] s_hexFormat = { 'x', '4' };
+        private static readonly StandardFormat s_hexStandardFormat = new StandardFormat('x', 4);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool NeedsEscaping(byte value) => AllowList[value] == 0;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool NeedsEscaping(char value) => value > byte.MaxValue || AllowList[value] == 0;
+
+        public static int NeedsEscaping(ReadOnlySpan<byte> value)
+        {
+            int idx;
+            for (idx = 0; idx < value.Length; idx++)
+            {
+                if (NeedsEscaping(value[idx]))
+                {
+                    goto Return;
+                }
+            }
+
+            idx = -1; // all characters allowed
+
+        Return:
+            return idx;
+        }
+
+        public static int NeedsEscaping(ReadOnlySpan<char> value)
+        {
+            int idx;
+            for (idx = 0; idx < value.Length; idx++)
+            {
+                if (NeedsEscaping(value[idx]))
+                {
+                    goto Return;
+                }
+            }
+
+            idx = -1; // all characters allowed
+
+        Return:
+            return idx;
+        }
+
+        public static int GetMaxEscapedLength(int textLength, int firstIndexToEscape)
+        {
+            Debug.Assert(textLength > 0);
+            Debug.Assert(firstIndexToEscape >= 0 && firstIndexToEscape < textLength);
+            return firstIndexToEscape + JsonConstants.MaxExpansionFactorWhileEscaping * (textLength - firstIndexToEscape);
+        }
+
+        public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, out int written)
+        {
+            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
+
+            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
+            written = indexOfFirstByteToEscape;
+            int consumed = indexOfFirstByteToEscape;
+
+            while (consumed < value.Length)
+            {
+                byte val = value[consumed];
+                if (NeedsEscaping(val))
+                {
+                    consumed += EscapeNextBytes(value.Slice(consumed), destination, ref written);
+                }
+                else
+                {
+                    destination[written] = val;
+                    written++;
+                    consumed++;
+                }
+            }
+        }
+
+        private static int EscapeNextBytes(ReadOnlySpan<byte> value, Span<byte> destination, ref int written)
+        {
+            SequenceValidity status = PeekFirstSequence(value, out int numBytesConsumed, out Rune rune);
+            if (status != SequenceValidity.WellFormed)
+                ThrowHelper.ThrowArgumentException_InvalidUTF8(value);
+
+            destination[written++] = (byte)'\\';
+            int scalar = rune.Value;
+            switch (scalar)
+            {
+                case JsonConstants.LineFeed:
+                    destination[written++] = (byte)'n';
+                    break;
+                case JsonConstants.CarriageReturn:
+                    destination[written++] = (byte)'r';
+                    break;
+                case JsonConstants.Tab:
+                    destination[written++] = (byte)'t';
+                    break;
+                case JsonConstants.BackSlash:
+                    destination[written++] = (byte)'\\';
+                    break;
+                case JsonConstants.BackSpace:
+                    destination[written++] = (byte)'b';
+                    break;
+                case JsonConstants.FormFeed:
+                    destination[written++] = (byte)'f';
+                    break;
+                default:
+                    destination[written++] = (byte)'u';
+                    if (scalar < JsonConstants.UnicodePlane01StartValue)
+                    {
+                        bool result = Utf8Formatter.TryFormat(scalar, destination.Slice(written), out int bytesWritten, format: s_hexStandardFormat);
+                        Debug.Assert(result);
+                        Debug.Assert(bytesWritten == 4);
+                        written += bytesWritten;
+                    }
+                    else
+                    {
+                        // Divide by 0x400 to shift right by 10 in order to find the surrogate pairs from the scalar
+                        // High surrogate = ((scalar -  0x10000) / 0x400) + D800
+                        // Low surrogate = ((scalar -  0x10000) % 0x400) + DC00
+                        int quotient = Math.DivRem(scalar - JsonConstants.UnicodePlane01StartValue, JsonConstants.ShiftRightBy10, out int remainder);
+                        int firstChar = quotient + JsonConstants.HighSurrogateStartValue;
+                        int nextChar = remainder + JsonConstants.LowSurrogateStartValue;
+                        bool result = Utf8Formatter.TryFormat(firstChar, destination.Slice(written), out int bytesWritten, format: s_hexStandardFormat);
+                        Debug.Assert(result);
+                        Debug.Assert(bytesWritten == 4);
+                        written += bytesWritten;
+                        destination[written++] = (byte)'\\';
+                        destination[written++] = (byte)'u';
+                        result = Utf8Formatter.TryFormat(nextChar, destination.Slice(written), out bytesWritten, format: s_hexStandardFormat);
+                        Debug.Assert(result);
+                        Debug.Assert(bytesWritten == 4);
+                        written += bytesWritten;
+                    }
+                    break;
+            }
+            return numBytesConsumed;
+        }
+
+        private static bool IsAsciiValue(byte value) => value < 0x80;
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff <paramref name="value"/> is a UTF-8 continuation byte.
+        /// A UTF-8 continuation byte is a byte whose value is in the range 0x80-0xBF, inclusive.
+        /// </summary>
+        private static bool IsUtf8ContinuationByte(byte value) => (value & 0xC0) == 0x80;
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff <paramref name="value"/> is between
+        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool IsInRangeInclusive(byte value, byte lowerBound, byte upperBound)
+            => ((byte)(value - lowerBound) <= (byte)(upperBound - lowerBound));
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff <paramref name="value"/> is between
+        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound)
+            => (value - lowerBound) <= (upperBound - lowerBound);
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff the low word of <paramref name="char"/> is a UTF-16 surrogate.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool IsLowWordSurrogate(uint @char)
+            => (@char & 0xF800U) == 0xD800U;
+
+        public static SequenceValidity PeekFirstSequence(ReadOnlySpan<byte> data, out int numBytesConsumed, out Rune rune)
+        {
+            // This method is implemented to match the behavior of System.Text.Encoding.UTF8 in terms of
+            // how many bytes it consumes when reporting invalid sequences. The behavior is as follows:
+            //
+            // - Some bytes are *always* invalid (ranges [ C0..C1 ] and [ F5..FF ]), and when these
+            //   are encountered it's an invalid sequence of length 1.
+            //
+            // - Multi-byte sequences which are overlong are reported as an invalid sequence of length 2,
+            //   since per the Unicode Standard Table 3-7 it's always possible to tell these by the second byte.
+            //   Exception: Sequences which begin with [ C0..C1 ] are covered by the above case, thus length 1.
+            //
+            // - Multi-byte sequences which are improperly terminated (no continuation byte when one is
+            //   expected) are reported as invalid sequences up to and including the last seen continuation byte.
+
+            rune = Rune.ReplacementChar;
+
+            if (data.IsEmpty)
+            {
+                // No data to peek at
+                numBytesConsumed = 0;
+                return SequenceValidity.Empty;
+            }
+
+            byte firstByte = data[0];
+
+            if (IsAsciiValue(firstByte))
+            {
+                // ASCII byte = well-formed one-byte sequence.
+                rune = new Rune(firstByte);
+                numBytesConsumed = 1;
+                return SequenceValidity.WellFormed;
+            }
+
+            if (!IsInRangeInclusive(firstByte, (byte)0xC2U, (byte)0xF4U))
+            {
+                // Standalone continuation byte or "always invalid" byte = ill-formed one-byte sequence.
+                goto InvalidOneByteSequence;
+            }
+
+            // At this point, we know we're working with a multi-byte sequence,
+            // and we know that at least the first byte is potentially valid.
+
+            if (data.Length < 2)
+            {
+                // One byte of an incomplete multi-byte sequence.
+                goto OneByteOfIncompleteMultiByteSequence;
+            }
+
+            byte secondByte = data[1];
+
+            if (!IsUtf8ContinuationByte(secondByte))
+            {
+                // One byte of an improperly terminated multi-byte sequence.
+                goto InvalidOneByteSequence;
+            }
+
+            if (firstByte < (byte)0xE0U)
+            {
+                // Well-formed two-byte sequence.
+                rune = new Rune((((uint)firstByte & 0x1FU) << 6) | ((uint)secondByte & 0x3FU));
+                numBytesConsumed = 2;
+                return SequenceValidity.WellFormed;
+            }
+
+            if (firstByte < (byte)0xF0U)
+            {
+                // Start of a three-byte sequence.
+                // Need to check for overlong or surrogate sequences.
+
+                uint scalar = (((uint)firstByte & 0x0FU) << 12) | (((uint)secondByte & 0x3FU) << 6);
+                if (scalar < 0x800U || IsLowWordSurrogate(scalar))
+                {
+                    goto OverlongOutOfRangeOrSurrogateSequence;
+                }
+
+                // At this point, we have a valid two-byte start of a three-byte sequence.
+
+                if (data.Length < 3)
+                {
+                    // Two bytes of an incomplete three-byte sequence.
+                    goto TwoBytesOfIncompleteMultiByteSequence;
+                }
+                else
+                {
+                    byte thirdByte = data[2];
+                    if (IsUtf8ContinuationByte(thirdByte))
+                    {
+                        // Well-formed three-byte sequence.
+                        scalar |= (uint)thirdByte & 0x3FU;
+                        rune = new Rune(scalar);
+                        numBytesConsumed = 3;
+                        return SequenceValidity.WellFormed;
+                    }
+                    else
+                    {
+                        // Two bytes of improperly terminated multi-byte sequence.
+                        goto InvalidTwoByteSequence;
+                    }
+                }
+            }
+
+            {
+                // Start of four-byte sequence.
+                // Need to check for overlong or out-of-range sequences.
+
+                uint scalar = (((uint)firstByte & 0x07U) << 18) | (((uint)secondByte & 0x3FU) << 12);
+                if (!IsInRangeInclusive(scalar, 0x10000U, 0x10FFFFU))
+                {
+                    goto OverlongOutOfRangeOrSurrogateSequence;
+                }
+
+                // At this point, we have a valid two-byte start of a four-byte sequence.
+
+                if (data.Length < 3)
+                {
+                    // Two bytes of an incomplete four-byte sequence.
+                    goto TwoBytesOfIncompleteMultiByteSequence;
+                }
+                else
+                {
+                    byte thirdByte = data[2];
+                    if (IsUtf8ContinuationByte(thirdByte))
+                    {
+                        // Valid three-byte start of a four-byte sequence.
+
+                        if (data.Length < 4)
+                        {
+                            // Three bytes of an incomplete four-byte sequence.
+                            goto ThreeBytesOfIncompleteMultiByteSequence;
+                        }
+                        else
+                        {
+                            byte fourthByte = data[3];
+                            if (IsUtf8ContinuationByte(fourthByte))
+                            {
+                                // Well-formed four-byte sequence.
+                                scalar |= (((uint)thirdByte & 0x3FU) << 6) | ((uint)fourthByte & 0x3FU);
+                                rune = new Rune(scalar);
+                                numBytesConsumed = 4;
+                                return SequenceValidity.WellFormed;
+                            }
+                            else
+                            {
+                                // Three bytes of an improperly terminated multi-byte sequence.
+                                goto InvalidThreeByteSequence;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // Two bytes of improperly terminated multi-byte sequence.
+                        goto InvalidTwoByteSequence;
+                    }
+                }
+            }
+
+        // Everything below here is error handling.
+
+        InvalidOneByteSequence:
+            numBytesConsumed = 1;
+            return SequenceValidity.Invalid;
+
+        InvalidTwoByteSequence:
+        OverlongOutOfRangeOrSurrogateSequence:
+            numBytesConsumed = 2;
+            return SequenceValidity.Invalid;
+
+        InvalidThreeByteSequence:
+            numBytesConsumed = 3;
+            return SequenceValidity.Invalid;
+
+        OneByteOfIncompleteMultiByteSequence:
+            numBytesConsumed = 1;
+            return SequenceValidity.Incomplete;
+
+        TwoBytesOfIncompleteMultiByteSequence:
+            numBytesConsumed = 2;
+            return SequenceValidity.Incomplete;
+
+        ThreeBytesOfIncompleteMultiByteSequence:
+            numBytesConsumed = 3;
+            return SequenceValidity.Incomplete;
+        }
+
+        public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, out int written)
+        {
+            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
+
+            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
+            written = indexOfFirstByteToEscape;
+            int consumed = indexOfFirstByteToEscape;
+
+            while (consumed < value.Length)
+            {
+                char val = value[consumed];
+                if (NeedsEscaping(val))
+                {
+                    EscapeNextChars(value, val, destination, ref consumed, ref written);
+                }
+                else
+                {
+                    destination[written++] = val;
+                }
+                consumed++;
+            }
+        }
+
+        private static void EscapeNextChars(ReadOnlySpan<char> value, int firstChar, Span<char> destination, ref int consumed, ref int written)
+        {
+            int nextChar = -1;
+            if (IsInRangeInclusive(firstChar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
+            {
+                consumed++;
+                if (value.Length <= consumed || firstChar >= JsonConstants.LowSurrogateStartValue)
+                {
+                    ThrowHelper.ThrowArgumentException_InvalidUTF16(firstChar);
+                }
+
+                nextChar = value[consumed];
+                if (!IsInRangeInclusive(nextChar, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
+                {
+                    ThrowHelper.ThrowArgumentException_InvalidUTF16(nextChar);
+                }
+            }
+
+            destination[written++] = '\\';
+            switch (firstChar)
+            {
+                case JsonConstants.LineFeed:
+                    destination[written++] = 'n';
+                    break;
+                case JsonConstants.CarriageReturn:
+                    destination[written++] = 'r';
+                    break;
+                case JsonConstants.Tab:
+                    destination[written++] = 't';
+                    break;
+                case JsonConstants.BackSlash:
+                    destination[written++] = '\\';
+                    break;
+                case JsonConstants.BackSpace:
+                    destination[written++] = 'b';
+                    break;
+                case JsonConstants.FormFeed:
+                    destination[written++] = 'f';
+                    break;
+                default:
+                    destination[written++] = 'u';
+                    firstChar.TryFormat(destination.Slice(written), out int charsWritten, s_hexFormat);
+                    Debug.Assert(charsWritten == 4);
+                    written += charsWritten;
+                    if (nextChar != -1)
+                    {
+                        destination[written++] = '\\';
+                        destination[written++] = 'u';
+                        nextChar.TryFormat(destination.Slice(written), out charsWritten, s_hexFormat);
+                        Debug.Assert(charsWritten == 4);
+                        written += charsWritten;
+                    }
+                    break;
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool IsInRangeInclusive(int ch, int start, int end)
+        {
+            return (uint)(ch - start) <= (uint)(end - start);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Transcoding.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Transcoding.cs
new file mode 100644 (file)
index 0000000..9285ee0
--- /dev/null
@@ -0,0 +1,334 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json
+{
+    internal static partial class JsonWriterHelper
+    {
+        // TODO: Replace this with publicly shipping implementation: https://github.com/dotnet/corefx/issues/34094
+        /// <summary>
+        /// Converts a span containing a sequence of UTF-16 bytes into UTF-8 bytes.
+        ///
+        /// This method will consume as many of the input bytes as possible.
+        ///
+        /// On successful exit, the entire input was consumed and encoded successfully. In this case, <paramref name="bytesConsumed"/> will be
+        /// equal to the length of the <paramref name="utf16Source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to
+        /// the <paramref name="utf8Destination"/>.
+        /// </summary>
+        /// <param name="utf16Source">A span containing a sequence of UTF-16 bytes.</param>
+        /// <param name="utf8Destination">A span to write the UTF-8 bytes into.</param>
+        /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="utf16Source"/>.</param>
+        /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="utf8Destination"/></param>
+        /// <returns>A <see cref="OperationStatus"/> value representing the state of the conversion.</returns>
+        public unsafe static OperationStatus ToUtf8(ReadOnlySpan<byte> utf16Source, Span<byte> utf8Destination, out int bytesConsumed, out int bytesWritten)
+        {
+            //
+            //
+            // KEEP THIS IMPLEMENTATION IN SYNC WITH https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs#L841
+            //
+            //
+            fixed (byte* chars = &MemoryMarshal.GetReference(utf16Source))
+            fixed (byte* bytes = &MemoryMarshal.GetReference(utf8Destination))
+            {
+                char* pSrc = (char*)chars;
+                byte* pTarget = bytes;
+
+                char* pEnd = (char*)(chars + utf16Source.Length);
+                byte* pAllocatedBufferEnd = pTarget + utf8Destination.Length;
+
+                // assume that JIT will enregister pSrc, pTarget and ch
+
+                // Entering the fast encoding loop incurs some overhead that does not get amortized for small
+                // number of characters, and the slow encoding loop typically ends up running for the last few
+                // characters anyway since the fast encoding loop needs 5 characters on input at least.
+                // Thus don't use the fast decoding loop at all if we don't have enough characters. The threashold
+                // was choosen based on performance testing.
+                // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop.
+                while (pEnd - pSrc > 13)
+                {
+                    // we need at least 1 byte per character, but Convert might allow us to convert
+                    // only part of the input, so try as much as we can.  Reduce charCount if necessary
+                    int available = Math.Min(PtrDiff(pEnd, pSrc), PtrDiff(pAllocatedBufferEnd, pTarget));
+
+                    // FASTLOOP:
+                    // - optimistic range checks
+                    // - fallbacks to the slow loop for all special cases, exception throwing, etc.
+
+                    // To compute the upper bound, assume that all characters are ASCII characters at this point,
+                    //  the boundary will be decreased for every non-ASCII character we encounter
+                    // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates
+                    // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop.
+                    char* pStop = pSrc + available - 5;
+                    if (pSrc >= pStop)
+                        break;
+
+                    do
+                    {
+                        int ch = *pSrc;
+                        pSrc++;
+
+                        if (ch > 0x7F)
+                        {
+                            goto LongCode;
+                        }
+                        *pTarget = (byte)ch;
+                        pTarget++;
+
+                        // get pSrc aligned
+                        if ((unchecked((int)pSrc) & 0x2) != 0)
+                        {
+                            ch = *pSrc;
+                            pSrc++;
+                            if (ch > 0x7F)
+                            {
+                                goto LongCode;
+                            }
+                            *pTarget = (byte)ch;
+                            pTarget++;
+                        }
+
+                        // Run 4 characters at a time!
+                        while (pSrc < pStop)
+                        {
+                            ch = *(int*)pSrc;
+                            int chc = *(int*)(pSrc + 2);
+                            if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0)
+                            {
+                                goto LongCodeWithMask;
+                            }
+
+                            // Unfortunately, this is endianess sensitive
+#if BIGENDIAN
+                            *pTarget = (byte)(ch >> 16);
+                            *(pTarget + 1) = (byte)ch;
+                            pSrc += 4;
+                            *(pTarget + 2) = (byte)(chc >> 16);
+                            *(pTarget + 3) = (byte)chc;
+                            pTarget += 4;
+#else // BIGENDIAN
+                            *pTarget = (byte)ch;
+                            *(pTarget + 1) = (byte)(ch >> 16);
+                            pSrc += 4;
+                            *(pTarget + 2) = (byte)chc;
+                            *(pTarget + 3) = (byte)(chc >> 16);
+                            pTarget += 4;
+#endif // BIGENDIAN
+                        }
+                        continue;
+
+                    LongCodeWithMask:
+#if BIGENDIAN
+                        // be careful about the sign extension
+                        ch = (int)(((uint)ch) >> 16);
+#else // BIGENDIAN
+                        ch = (char)ch;
+#endif // BIGENDIAN
+                        pSrc++;
+
+                        if (ch > 0x7F)
+                        {
+                            goto LongCode;
+                        }
+                        *pTarget = (byte)ch;
+                        pTarget++;
+                        continue;
+
+                    LongCode:
+                        // use separate helper variables for slow and fast loop so that the jit optimizations
+                        // won't get confused about the variable lifetimes
+                        int chd;
+                        if (ch <= 0x7FF)
+                        {
+                            // 2 byte encoding
+                            chd = unchecked((sbyte)0xC0) | (ch >> 6);
+                        }
+                        else
+                        {
+                            // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch))
+                            if (!IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd))
+                            {
+                                // 3 byte encoding
+                                chd = unchecked((sbyte)0xE0) | (ch >> 12);
+                            }
+                            else
+                            {
+                                // 4 byte encoding - high surrogate + low surrogate
+                                // if (!IsHighSurrogate(ch))
+                                if (ch > JsonConstants.HighSurrogateEnd)
+                                {
+                                    // low without high -> bad
+                                    goto InvalidData;
+                                }
+
+                                chd = *pSrc;
+
+                                // if (!IsLowSurrogate(chd)) {
+                                if (!IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd))
+                                {
+                                    // high not followed by low -> bad
+                                    goto InvalidData;
+                                }
+
+                                pSrc++;
+
+                                ch = chd + (ch << 10) +
+                                    (0x10000
+                                    - JsonConstants.LowSurrogateStart
+                                    - (JsonConstants.HighSurrogateStart << 10));
+
+                                *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18));
+                                // pStop - this byte is compensated by the second surrogate character
+                                // 2 input chars require 4 output bytes.  2 have been anticipated already
+                                // and 2 more will be accounted for by the 2 pStop-- calls below.
+                                pTarget++;
+
+                                chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F;
+                            }
+                            *pTarget = (byte)chd;
+                            pStop--;                    // 3 byte sequence for 1 char, so need pStop-- and the one below too.
+                            pTarget++;
+
+                            chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F;
+                        }
+                        *pTarget = (byte)chd;
+                        pStop--;                        // 2 byte sequence for 1 char so need pStop--.
+
+                        *(pTarget + 1) = (byte)(unchecked((sbyte)0x80) | ch & 0x3F);
+                        // pStop - this byte is already included
+
+                        pTarget += 2;
+                    }
+                    while (pSrc < pStop);
+
+                    Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd");
+                }
+
+                while (pSrc < pEnd)
+                {
+                    // SLOWLOOP: does all range checks, handles all special cases, but it is slow
+
+                    // read next char. The JIT optimization seems to be getting confused when
+                    // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
+                    int ch = *pSrc;
+                    pSrc++;
+
+                    if (ch <= 0x7F)
+                    {
+                        if (pAllocatedBufferEnd - pTarget <= 0)
+                            goto DestinationFull;
+
+                        *pTarget = (byte)ch;
+                        pTarget++;
+                        continue;
+                    }
+
+                    int chd;
+                    if (ch <= 0x7FF)
+                    {
+                        if (pAllocatedBufferEnd - pTarget <= 1)
+                            goto DestinationFull;
+
+                        // 2 byte encoding
+                        chd = unchecked((sbyte)0xC0) | (ch >> 6);
+                    }
+                    else
+                    {
+                        // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch))
+                        if (!IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd))
+                        {
+                            if (pAllocatedBufferEnd - pTarget <= 2)
+                                goto DestinationFull;
+
+                            // 3 byte encoding
+                            chd = unchecked((sbyte)0xE0) | (ch >> 12);
+                        }
+                        else
+                        {
+                            if (pAllocatedBufferEnd - pTarget <= 3)
+                                goto DestinationFull;
+
+                            // 4 byte encoding - high surrogate + low surrogate
+                            // if (!IsHighSurrogate(ch))
+                            if (ch > JsonConstants.HighSurrogateEnd)
+                            {
+                                // low without high -> bad
+                                goto InvalidData;
+                            }
+
+                            if (pSrc >= pEnd)
+                                goto NeedMoreData;
+
+                            chd = *pSrc;
+
+                            // if (!IsLowSurrogate(chd)) {
+                            if (!IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd))
+                            {
+                                // high not followed by low -> bad
+                                goto InvalidData;
+                            }
+
+                            pSrc++;
+
+                            ch = chd + (ch << 10) +
+                                (0x10000
+                                - JsonConstants.LowSurrogateStart
+                                - (JsonConstants.HighSurrogateStart << 10));
+
+                            *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18));
+                            pTarget++;
+
+                            chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F;
+                        }
+                        *pTarget = (byte)chd;
+                        pTarget++;
+
+                        chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F;
+                    }
+
+                    *pTarget = (byte)chd;
+                    *(pTarget + 1) = (byte)(unchecked((sbyte)0x80) | ch & 0x3F);
+
+                    pTarget += 2;
+                }
+
+                bytesConsumed = (int)((byte*)pSrc - chars);
+                bytesWritten = (int)(pTarget - bytes);
+                return OperationStatus.Done;
+
+            InvalidData:
+                bytesConsumed = (int)((byte*)(pSrc - 1) - chars);
+                bytesWritten = (int)(pTarget - bytes);
+                return OperationStatus.InvalidData;
+
+            DestinationFull:
+                bytesConsumed = (int)((byte*)(pSrc - 1) - chars);
+                bytesWritten = (int)(pTarget - bytes);
+                return OperationStatus.DestinationTooSmall;
+
+            NeedMoreData:
+                bytesConsumed = (int)((byte*)(pSrc - 1) - chars);
+                bytesWritten = (int)(pTarget - bytes);
+                return OperationStatus.NeedMoreData;
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private unsafe static int PtrDiff(char* a, char* b)
+        {
+            return (int)(((uint)((byte*)a - (byte*)b)) >> 1);
+        }
+
+        // byte* flavor just for parity
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private unsafe static int PtrDiff(byte* a, byte* b)
+        {
+            return (int)(a - b);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
new file mode 100644 (file)
index 0000000..962731e
--- /dev/null
@@ -0,0 +1,119 @@
+// 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
+{
+    internal static partial class JsonWriterHelper
+    {
+        public static bool TryWriteIndentation(Span<byte> buffer, int indent, out int bytesWritten)
+        {
+            Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0);
+
+            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
+            {
+                int i = 0;
+                while (i < buffer.Length - 1)
+                {
+                    buffer[i++] = JsonConstants.Space;
+                    buffer[i++] = JsonConstants.Space;
+                }
+                bytesWritten = i;
+                return false;
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateProperty(ReadOnlySpan<byte> propertyName)
+        {
+            if (propertyName.Length > JsonConstants.MaxTokenSize)
+                ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateValue(ReadOnlySpan<byte> value)
+        {
+            if (value.Length > JsonConstants.MaxTokenSize)
+                ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateDouble(double value)
+        {
+            if (!double.IsFinite(value))
+                ThrowHelper.ThrowArgumentException_ValueNotSupported();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateSingle(float value)
+        {
+            if (!float.IsFinite(value))
+                ThrowHelper.ThrowArgumentException_ValueNotSupported();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateProperty(ReadOnlySpan<char> propertyName)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize)
+                ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidateValue(ReadOnlySpan<char> value)
+        {
+            if (value.Length > JsonConstants.MaxCharacterTokenSize)
+                ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || value.Length > JsonConstants.MaxTokenSize)
+                ThrowHelper.ThrowArgumentException(propertyName, value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<char> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxTokenSize || value.Length > JsonConstants.MaxCharacterTokenSize)
+                ThrowHelper.ThrowArgumentException(propertyName, value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxTokenSize || value.Length > JsonConstants.MaxTokenSize)
+                ThrowHelper.ThrowArgumentException(propertyName, value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || value.Length > JsonConstants.MaxCharacterTokenSize)
+                ThrowHelper.ThrowArgumentException(propertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs
new file mode 100644 (file)
index 0000000..2642899
--- /dev/null
@@ -0,0 +1,69 @@
+// 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>
+    /// Provides the ability for the user to define custom behavior when writing JSON
+    /// using the <see cref="Utf8JsonWriter"/>. By default, the JSON is written without
+    /// any indentation or extra white space. Also, the <see cref="Utf8JsonWriter"/> will
+    /// throw an exception if the user attempts to write structurally invalid JSON.
+    /// </summary>
+    public struct JsonWriterOptions
+    {
+        private int _optionsMask;
+
+        /// <summary>
+        /// Defines whether the <see cref="Utf8JsonWriter"/> should pretty print the JSON which includes:
+        /// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
+        /// By default, the JSON is written without any extra white space.
+        /// </summary>
+        public bool Indented
+        {
+            get
+            {
+                return (_optionsMask & IndentBit) != 0;
+            }
+            set
+            {
+                if (value)
+                    _optionsMask |= IndentBit;
+                else
+                    _optionsMask &= ~IndentBit;
+            }
+        }
+
+        /// <summary>
+        /// Defines whether the <see cref="Utf8JsonWriter"/> should skip structural validation and allow
+        /// the user to write invalid JSON, when set to true. If set to false, any attempts to write invalid JSON will result in
+        /// a <exception cref="InvalidOperationException"/> to be thrown.
+        /// </summary>
+        /// <remarks>
+        /// If the JSON being written is known to be correct,
+        /// then skipping validation (by setting it to true) could improve performance.
+        /// An example of invalid JSON where the writer will throw (when SkipValidation
+        /// is set to false) is when you write a value within a JSON object
+        /// without a property name. 
+        /// </remarks>
+        public bool SkipValidation
+        {
+            get
+            {
+                return (_optionsMask & SkipValidationBit) != 0;
+            }
+            set
+            {
+                if (value)
+                    _optionsMask |= SkipValidationBit;
+                else
+                    _optionsMask &= ~SkipValidationBit;
+            }
+        }
+
+        internal bool IndentedOrNotSkipValidation => _optionsMask != SkipValidationBit; // Equivalent to: Indented || !SkipValidation;
+
+        private const int IndentBit = 1;
+        private const int SkipValidationBit = 2;
+    }
+}
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
new file mode 100644 (file)
index 0000000..52e98d4
--- /dev/null
@@ -0,0 +1,74 @@
+// 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;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/SequenceValidity.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/SequenceValidity.cs
new file mode 100644 (file)
index 0000000..d96c482
--- /dev/null
@@ -0,0 +1,55 @@
+// 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.Buffers.Text
+{
+    /// <summary>
+    /// Represents the validity of a UTF code unit sequence.
+    /// </summary>
+    internal enum SequenceValidity
+    {
+        /// <summary>
+        /// The sequence is empty.
+        /// </summary>
+        Empty = 0,
+
+        /// <summary>
+        /// The sequence is well-formed and unambiguously represents a proper Unicode scalar value.
+        /// </summary>
+        /// <remarks>
+        /// [ 20 ] (U+0020 SPACE) is a well-formed UTF-8 sequence.
+        /// [ C3 A9 ] (U+00E9 LATIN SMALL LETTER E WITH ACUTE) is a well-formed UTF-8 sequence.
+        /// [ F0 9F 98 80 ] (U+1F600 GRINNING FACE) is a well-formed UTF-8 sequence.
+        /// [ D83D DE00 ] (U+1F600 GRINNING FACE) is a well-formed UTF-16 sequence.
+        /// </remarks>
+        WellFormed = 1,
+
+        /// <summary>
+        /// The sequence is not well-formed on its own, but it could appear as a prefix
+        /// of a longer well-formed sequence. More code units are needed to make a proper
+        /// determination as to whether this sequence is well-formed. Incomplete sequences
+        /// can only appear at the end of a string.
+        /// </summary>
+        /// <remarks>
+        /// [ C2 ] is an incomplete UTF-8 sequence if it is followed by nothing.
+        /// [ F0 9F ] is an incomplete UTF-8 sequence if it is followed by nothing.
+        /// [ D83D ] is an incomplete UTF-16 sequence if it is followed by nothing.
+        /// </remarks>
+        Incomplete = 2,
+
+        /// <summary>
+        /// The sequence is never well-formed anywhere, or this sequence can never appear as a prefix
+        /// of a longer well-formed sequence, or the sequence was improperly terminated by the code
+        /// unit which appeared immediately after this sequence.
+        /// </summary>
+        /// <remarks>
+        /// [ 80 ] is an invalid UTF-8 sequence (code unit cannot appear at start of sequence).
+        /// [ FE ] is an invalid UTF-8 sequence (sequence is never well-formed anywhere in UTF-8 string).
+        /// [ C2 ] is an invalid UTF-8 sequence if it is followed by [ 20 ] (sequence improperly terminated).
+        /// [ ED A0 ] is an invalid UTF-8 sequence (sequence is never well-formed anywhere in UTF-8 string).
+        /// [ DE00 ] is an invalid UTF-16 sequence (code unit cannot appear at start of sequence).
+        /// </remarks>
+        Invalid = 3
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
new file mode 100644 (file)
index 0000000..1e345c6
--- /dev/null
@@ -0,0 +1,285 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(propertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> propertyName, DateTime value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<char> propertyName, DateTime value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, DateTime value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTime value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(propertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, DateTime value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValue(DateTime value, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            FormatLoop(value, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+        }
+
+        private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O');
+
+        private void FormatLoop(DateTime value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten, s_dateTimeStandardFormat))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDateTimeLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten, s_dateTimeStandardFormat);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
new file mode 100644 (file)
index 0000000..16d61c6
--- /dev/null
@@ -0,0 +1,283 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(propertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> propertyName, DateTimeOffset value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<char> propertyName, DateTimeOffset value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTimeOffset value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(propertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValue(DateTimeOffset value, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            FormatLoop(value, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+        }
+
+        private void FormatLoop(DateTimeOffset value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten, s_dateTimeStandardFormat))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDateTimeOffsetLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten, s_dateTimeStandardFormat);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
new file mode 100644 (file)
index 0000000..f03e84d
--- /dev/null
@@ -0,0 +1,266 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(propertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<char> propertyName, decimal value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, decimal value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, decimal value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, decimal value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, decimal value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, decimal value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, decimal value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, decimal value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, decimal value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueFormatLoop(decimal value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDecimalLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
new file mode 100644 (file)
index 0000000..ea19ce3
--- /dev/null
@@ -0,0 +1,268 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+            JsonWriterHelper.ValidateDouble(value);
+
+            if (escape)
+            {
+                WriteNumberEscape(propertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+            JsonWriterHelper.ValidateDouble(value);
+
+            if (escape)
+            {
+                WriteNumberEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<char> propertyName, double value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, double value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, double value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, double value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, double value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, double value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, double value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, double value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, double value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, double value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueFormatLoop(double value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDoubleLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
new file mode 100644 (file)
index 0000000..5853265
--- /dev/null
@@ -0,0 +1,268 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+            JsonWriterHelper.ValidateSingle(value);
+
+            if (escape)
+            {
+                WriteNumberEscape(propertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+            JsonWriterHelper.ValidateSingle(value);
+
+            if (escape)
+            {
+                WriteNumberEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<char> propertyName, float value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, float value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, float value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, float value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, float value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<byte> propertyName, float value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, float value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, float value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, float value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, float value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueFormatLoop(float value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatSingleLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
new file mode 100644 (file)
index 0000000..c201b6a
--- /dev/null
@@ -0,0 +1,283 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(propertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> propertyName, Guid value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, Guid value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStringEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<char> propertyName, Guid value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, Guid value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> propertyName, Guid value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(propertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, Guid value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, Guid value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, Guid value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, Guid value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, Guid value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValue(Guid value, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            FormatLoop(value, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+        }
+
+        private void FormatLoop(Guid value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatGuidLength);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs
new file mode 100644 (file)
index 0000000..a7191a5
--- /dev/null
@@ -0,0 +1,263 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ValidatePropertyNameAndDepth(ReadOnlySpan<char> propertyName)
+        {
+            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || CurrentDepth >= JsonConstants.MaxWriterDepth)
+                ThrowHelper.ThrowInvalidOperationOrArgumentException(propertyName, _currentDepth);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ValidatePropertyNameAndDepth(ReadOnlySpan<byte> utf8PropertyName)
+        {
+            if (utf8PropertyName.Length > JsonConstants.MaxTokenSize || CurrentDepth >= JsonConstants.MaxWriterDepth)
+                ThrowHelper.ThrowInvalidOperationOrArgumentException(utf8PropertyName, _currentDepth);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ValidateWritingProperty()
+        {
+            if (!_writerOptions.SkipValidation)
+            {
+                if (!_inObject)
+                {
+                    Debug.Assert(_tokenType != JsonTokenType.StartObject);
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWritePropertyWithinArray, tokenType: _tokenType);
+                }
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ValidateWritingProperty(byte token)
+        {
+            if (!_writerOptions.SkipValidation)
+            {
+                if (!_inObject)
+                {
+                    Debug.Assert(_tokenType != JsonTokenType.StartObject);
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWritePropertyWithinArray, tokenType: _tokenType);
+                }
+                UpdateBitStackOnStart(token);
+            }
+        }
+
+        private int WritePropertyNameMinimized(ReadOnlySpan<byte> escapedPropertyName)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            CopyLoop(escapedPropertyName, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+
+            return idx;
+        }
+
+        private int WritePropertyNameIndented(ReadOnlySpan<byte> escapedPropertyName)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            CopyLoop(escapedPropertyName, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Space;
+
+            return idx;
+        }
+
+        private int WritePropertyNameMinimized(ReadOnlySpan<char> escapedPropertyName)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+
+            return idx;
+        }
+
+        private int WritePropertyNameIndented(ReadOnlySpan<char> escapedPropertyName)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.KeyValueSeperator;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Space;
+
+            return idx;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
new file mode 100644 (file)
index 0000000..13f3a16
--- /dev/null
@@ -0,0 +1,309 @@
+// 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.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            ReadOnlySpan<byte> span = JsonConstants.NullValue;
+
+            if (escape)
+            {
+                WriteLiteralEscape(propertyName, span);
+            }
+            else
+            {
+                WriteLiteralByOptions(propertyName, span);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Null;
+        }
+
+        /// <summary>
+        /// 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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            ReadOnlySpan<byte> span = JsonConstants.NullValue;
+
+            if (escape)
+            {
+                WriteLiteralEscape(utf8PropertyName, span);
+            }
+            else
+            {
+                WriteLiteralByOptions(utf8PropertyName, span);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Null;
+        }
+
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
+
+            if (escape)
+            {
+                WriteLiteralEscape(propertyName, span);
+            }
+            else
+            {
+                WriteLiteralByOptions(propertyName, span);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
+        }
+
+        /// <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="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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
+
+            if (escape)
+            {
+                WriteLiteralEscape(utf8PropertyName, span);
+            }
+            else
+            {
+                WriteLiteralByOptions(utf8PropertyName, span);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
+        }
+
+        private void WriteLiteralEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteLiteralEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteLiteralByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteLiteralEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteLiteralEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteLiteralByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteLiteralEscapeProperty(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteLiteralEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteLiteralByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
+        {
+            ValidateWritingProperty();
+            int idx;
+            if (_writerOptions.Indented)
+            {
+                idx = WritePropertyNameIndented(propertyName);
+            }
+            else
+            {
+                idx = WritePropertyNameMinimized(propertyName);
+            }
+
+            if (value.Length > _buffer.Length - idx)
+            {
+                AdvanceAndGrow(ref idx, value.Length);
+            }
+
+            value.CopyTo(_buffer.Slice(idx));
+            idx += value.Length;
+
+            Advance(idx);
+        }
+
+        private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
+        {
+            ValidateWritingProperty();
+            int idx;
+            if (_writerOptions.Indented)
+            {
+                idx = WritePropertyNameIndented(utf8PropertyName);
+            }
+            else
+            {
+                idx = WritePropertyNameMinimized(utf8PropertyName);
+            }
+
+            if (value.Length > _buffer.Length - idx)
+            {
+                AdvanceAndGrow(ref idx, value.Length);
+            }
+
+            value.CopyTo(_buffer.Slice(idx));
+            idx += value.Length;
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
new file mode 100644 (file)
index 0000000..be106a1
--- /dev/null
@@ -0,0 +1,320 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(propertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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);
+
+        /// <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>
+        /// <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>
+        /// <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);
+
+        private void WriteNumberEscape(ReadOnlySpan<char> propertyName, long value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, long value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, long value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, long value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, long value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, long value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, long value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, long value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, long value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, long value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueFormatLoop(long value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatInt64Length);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
new file mode 100644 (file)
index 0000000..f90c35b
--- /dev/null
@@ -0,0 +1,837 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidatePropertyAndValue(propertyName, value);
+
+            if (escape)
+            {
+                WriteStringEscape(propertyName, value);
+            }
+            else
+            {
+                WriteStringDontEscape(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <summary>
+        /// Writes the UTF-8 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="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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, utf8Value);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8PropertyName, utf8Value);
+            }
+            else
+            {
+                WriteStringDontEscape(utf8PropertyName, utf8Value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <summary>
+        /// Writes the 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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, value);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringDontEscape(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <summary>
+        /// Writes the 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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidatePropertyAndValue(propertyName, utf8Value);
+
+            if (escape)
+            {
+                WriteStringEscape(propertyName, utf8Value);
+            }
+            else
+            {
+                WriteStringDontEscape(propertyName, utf8Value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        /// <summary>
+        /// Writes the UTF-16 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>
+        /// <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);
+
+        /// <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>
+        /// <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);
+            }
+        }
+
+        private void WriteStringEscapeValueOnly(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> value, int firstEscapeIndex)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(firstEscapeIndex >= 0 && firstEscapeIndex < value.Length);
+
+            char[] valueArray = ArrayPool<char>.Shared.Rent(JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndex));
+            Span<char> escapedValue = valueArray;
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndex, out int written);
+
+            WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
+
+            ArrayPool<char>.Shared.Return(valueArray);
+        }
+
+        private void WriteStringEscapeValueOnly(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> utf8Value, int firstEscapeIndex)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(firstEscapeIndex >= 0 && firstEscapeIndex < utf8Value.Length);
+
+            byte[] valueArray = ArrayPool<byte>.Shared.Rent(JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndex));
+            Span<byte> escapedValue = valueArray;
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndex, out int written);
+
+            WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
+
+            ArrayPool<byte>.Shared.Return(valueArray);
+        }
+
+        private void WriteStringEscapeValueOnly(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> utf8Value, int firstEscapeIndex)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(firstEscapeIndex >= 0 && firstEscapeIndex < utf8Value.Length);
+
+            byte[] valueArray = ArrayPool<byte>.Shared.Rent(JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndex));
+            Span<byte> escapedValue = valueArray;
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndex, out int written);
+
+            WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
+
+            ArrayPool<byte>.Shared.Return(valueArray);
+        }
+
+        private void WriteStringEscapeValueOnly(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> value, int firstEscapeIndex)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(firstEscapeIndex >= 0 && firstEscapeIndex < value.Length);
+
+            char[] valueArray = ArrayPool<char>.Shared.Rent(JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndex));
+            Span<char> escapedValue = valueArray;
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndex, out int written);
+
+            WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
+
+            ArrayPool<char>.Shared.Return(valueArray);
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
+        {
+            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);
+
+            // Equivalent to: valueIdx != -1 || propertyIdx != -1
+            if (valueIdx + propertyIdx != -2)
+            {
+                WriteStringEscapePropertyOrValue(propertyName, value, propertyIdx, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
+        {
+            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);
+
+            // Equivalent to: valueIdx != -1 || propertyIdx != -1
+            if (valueIdx + propertyIdx != -2)
+            {
+                WriteStringEscapePropertyOrValue(utf8PropertyName, utf8Value, propertyIdx, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, utf8Value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
+        {
+            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);
+
+            // Equivalent to: valueIdx != -1 || propertyIdx != -1
+            if (valueIdx + propertyIdx != -2)
+            {
+                WriteStringEscapePropertyOrValue(propertyName, utf8Value, propertyIdx, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(propertyName, utf8Value);
+            }
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
+        {
+            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);
+
+            // Equivalent to: valueIdx != -1 || propertyIdx != -1
+            if (valueIdx + propertyIdx != -2)
+            {
+                WriteStringEscapePropertyOrValue(utf8PropertyName, value, propertyIdx, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringEscapePropertyOrValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value, int firstEscapeIndexProp, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+
+            char[] valueArray = null;
+            char[] propertyArray = null;
+
+            if (firstEscapeIndexVal != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+                value = escapedValue.Slice(0, written);
+            }
+
+            if (firstEscapeIndexProp != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                propertyName = escapedPropertyName.Slice(0, written);
+            }
+
+            WriteStringByOptions(propertyName, value);
+
+            if (valueArray != null)
+            {
+                ArrayPool<char>.Shared.Return(valueArray);
+            }
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapePropertyOrValue(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value, int firstEscapeIndexProp, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+
+            byte[] valueArray = null;
+            byte[] propertyArray = null;
+
+            if (firstEscapeIndexVal != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
+                utf8Value = escapedValue.Slice(0, written);
+            }
+
+            if (firstEscapeIndexProp != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                utf8PropertyName = escapedPropertyName.Slice(0, written);
+            }
+
+            WriteStringByOptions(utf8PropertyName, utf8Value);
+
+            if (valueArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(valueArray);
+            }
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapePropertyOrValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value, int firstEscapeIndexProp, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+
+            byte[] valueArray = null;
+            char[] propertyArray = null;
+
+            if (firstEscapeIndexVal != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
+                utf8Value = escapedValue.Slice(0, written);
+            }
+
+            if (firstEscapeIndexProp != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                propertyName = escapedPropertyName.Slice(0, written);
+            }
+
+            WriteStringByOptions(propertyName, utf8Value);
+
+            if (valueArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(valueArray);
+            }
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringEscapePropertyOrValue(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value, int firstEscapeIndexProp, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+
+            char[] valueArray = null;
+            byte[] propertyArray = null;
+
+            if (firstEscapeIndexVal != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+                value = escapedValue.Slice(0, written);
+            }
+
+            if (firstEscapeIndexProp != -1)
+            {
+                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);
+                    }
+                }
+                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                utf8PropertyName = escapedPropertyName.Slice(0, written);
+            }
+
+            WriteStringByOptions(utf8PropertyName, value);
+
+            if (valueArray != null)
+            {
+                ArrayPool<char>.Shared.Return(valueArray);
+            }
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(propertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8PropertyName, utf8Value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8PropertyName, utf8Value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(propertyName, utf8Value);
+            }
+            else
+            {
+                WriteStringMinimized(propertyName, utf8Value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> escapedValue)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> escapedValue)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<char> escapedValue)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<char> escapedValue)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValue(ReadOnlySpan<char> escapedValue, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+        }
+
+        private void WriteStringValue(ReadOnlySpan<byte> escapedValue, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+
+            CopyLoop(escapedValue, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Quote;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
new file mode 100644 (file)
index 0000000..ee85f7d
--- /dev/null
@@ -0,0 +1,326 @@
+// 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.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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>
+        /// <remarks>
+        /// 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);
+
+        /// <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>
+        /// <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>
+        /// <remarks>
+        /// 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)
+        {
+            JsonWriterHelper.ValidateProperty(propertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(propertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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="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>
+        /// <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>
+        /// <remarks>
+        /// 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)
+        {
+            JsonWriterHelper.ValidateProperty(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteNumberEscape(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        /// <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>
+        /// <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>
+        /// <remarks>
+        /// 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);
+
+        /// <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>
+        /// <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>
+        /// <remarks>
+        /// 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);
+
+        /// <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>
+        /// <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>
+        /// <remarks>
+        /// 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);
+
+        private void WriteNumberEscape(ReadOnlySpan<char> propertyName, ulong value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(propertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(propertyName, value);
+            }
+        }
+
+        private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, ulong value)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteNumberEscapeProperty(utf8PropertyName, value, propertyIdx);
+            }
+            else
+            {
+                WriteNumberByOptions(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, ulong value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, ulong value, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, ulong value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(propertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(propertyName, value);
+            }
+        }
+
+        private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, ulong value)
+        {
+            ValidateWritingProperty();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberIndented(utf8PropertyName, value);
+            }
+            else
+            {
+                WriteNumberMinimized(utf8PropertyName, value);
+            }
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<char> escapedPropertyName, ulong value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, ulong value)
+        {
+            int idx = WritePropertyNameMinimized(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, ulong value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, ulong value)
+        {
+            int idx = WritePropertyNameIndented(escapedPropertyName);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueFormatLoop(ulong value, ref int idx)
+        {
+            if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten))
+            {
+                AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatUInt64Length);
+                bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten);
+                Debug.Assert(result);
+            }
+            idx += bytesWritten;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs
new file mode 100644 (file)
index 0000000..dc749fa
--- /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.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified value is too large.
+        /// </exception>
+        public void WriteCommentValue(string value, bool escape = true)
+            => WriteCommentValue(value.AsSpan(), escape);
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified value is too large.
+        /// </exception>
+        public void WriteCommentValue(ReadOnlySpan<char> value, bool escape = true)
+        {
+            JsonWriterHelper.ValidateValue(value);
+
+            if (escape)
+            {
+                WriteCommentEscape(value);
+            }
+            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);
+            }
+        }
+
+        private void WriteCommentByOptions(ReadOnlySpan<char> value)
+        {
+            if (_writerOptions.Indented)
+            {
+                WriteCommentIndented(value);
+            }
+            else
+            {
+                WriteCommentMinimized(value);
+            }
+        }
+
+        private void WriteCommentMinimized(ReadOnlySpan<char> escapedValue)
+        {
+            int idx = 0;
+
+            WriteCommentValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteCommentIndented(ReadOnlySpan<char> escapedValue)
+        {
+            int idx = 0;
+
+            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);
+            }
+
+            WriteCommentValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteCommentEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length);
+
+            char[] valueArray = null;
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+
+            WriteCommentByOptions(escapedValue.Slice(0, written));
+
+            if (valueArray != null)
+            {
+                ArrayPool<char>.Shared.Return(valueArray);
+            }
+        }
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified value is too large.
+        /// </exception>
+        public void WriteCommentValue(ReadOnlySpan<byte> utf8Value, bool escape = true)
+        {
+            JsonWriterHelper.ValidateValue(utf8Value);
+
+            if (escape)
+            {
+                WriteCommentEscape(utf8Value);
+            }
+            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);
+            }
+        }
+
+        private void WriteCommentByOptions(ReadOnlySpan<byte> utf8Value)
+        {
+            if (_writerOptions.Indented)
+            {
+                WriteCommentIndented(utf8Value);
+            }
+            else
+            {
+                WriteCommentMinimized(utf8Value);
+            }
+        }
+
+        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)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
+
+            byte[] valueArray = null;
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
+
+            WriteCommentByOptions(escapedValue.Slice(0, written));
+
+            if (valueArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(valueArray);
+            }
+        }
+
+        private void WriteCommentValue(ReadOnlySpan<char> escapedValue, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Slash;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Asterisk;
+
+            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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Asterisk;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Slash;
+        }
+
+        private void WriteCommentValue(ReadOnlySpan<byte> escapedValue, ref int idx)
+        {
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Slash;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Asterisk;
+
+            CopyLoop(escapedValue, ref idx);
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Asterisk;
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.Slash;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs
new file mode 100644 (file)
index 0000000..d68d461
--- /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 System.Buffers;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="DateTime"/> value (as a JSON string) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON string as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <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 WriteStringValue(DateTime value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteStringValueIndented(value);
+            }
+            else
+            {
+                WriteStringValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringValueMinimized(DateTime value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValueIndented(DateTime value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs
new file mode 100644 (file)
index 0000000..3edbf64
--- /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 System.Buffers;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="DateTimeOffset"/> value (as a JSON string) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON string as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <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 WriteStringValue(DateTimeOffset value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteStringValueIndented(value);
+            }
+            else
+            {
+                WriteStringValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringValueMinimized(DateTimeOffset value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValueIndented(DateTimeOffset value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs
new file mode 100644 (file)
index 0000000..305cf85
--- /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 System.Buffers;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="decimal"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="decimal"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
+        /// </remarks>
+        public void WriteNumberValue(decimal value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberValueIndented(value);
+            }
+            else
+            {
+                WriteNumberValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberValueMinimized(decimal value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueIndented(decimal value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs
new file mode 100644 (file)
index 0000000..54fc046
--- /dev/null
@@ -0,0 +1,58 @@
+// 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;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="double"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
+        /// </remarks>
+        public void WriteNumberValue(double value)
+        {
+            JsonWriterHelper.ValidateDouble(value);
+
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberValueIndented(value);
+            }
+            else
+            {
+                WriteNumberValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberValueMinimized(double value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueIndented(double value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs
new file mode 100644 (file)
index 0000000..ad8dd12
--- /dev/null
@@ -0,0 +1,58 @@
+// 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;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="float"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
+        /// </remarks>
+        public void WriteNumberValue(float value)
+        {
+            JsonWriterHelper.ValidateSingle(value);
+
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberValueIndented(value);
+            }
+            else
+            {
+                WriteNumberValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberValueMinimized(float value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueIndented(float value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs
new file mode 100644 (file)
index 0000000..a6d17ce
--- /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 System.Buffers;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="Guid"/> value (as a JSON string) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON string as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <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 WriteStringValue(Guid value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteStringValueIndented(value);
+            }
+            else
+            {
+                WriteStringValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringValueMinimized(Guid value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringValueIndented(Guid value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteStringValue(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs
new file mode 100644 (file)
index 0000000..e59d0a0
--- /dev/null
@@ -0,0 +1,69 @@
+// 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.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        private void ValidateWritingValue()
+        {
+            if (!_writerOptions.SkipValidation)
+            {
+                if (_inObject)
+                {
+                    Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueWithinObject, tokenType: _tokenType);
+                }
+                else
+                {
+                    if (!_isNotPrimitive && _tokenType != JsonTokenType.None)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueAfterPrimitive, tokenType: _tokenType);
+                    }
+                }
+            }
+        }
+
+        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;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs
new file mode 100644 (file)
index 0000000..03d57f2
--- /dev/null
@@ -0,0 +1,78 @@
+// 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
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the JSON literal "null" as an element of a JSON array.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        public void WriteNullValue()
+        {
+            WriteLiteralByOptions(JsonConstants.NullValue);
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Null;
+        }
+
+        /// <summary>
+        /// Writes the <see cref="bool"/> value (as a JSON literal "true" or "false") as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON literal "true" or "false" as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        public void WriteBooleanValue(bool value)
+        {
+            if (value)
+            {
+                WriteLiteralByOptions(JsonConstants.TrueValue);
+                _tokenType = JsonTokenType.True;
+            }
+            else
+            {
+                WriteLiteralByOptions(JsonConstants.FalseValue);
+                _tokenType = JsonTokenType.False;
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+        }
+
+        private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8Value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteLiteralIndented(utf8Value);
+            }
+            else
+            {
+                WriteLiteralMinimized(utf8Value);
+            }
+        }
+
+        private void WriteLiteralMinimized(ReadOnlySpan<byte> utf8Value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            CopyLoop(utf8Value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteLiteralIndented(ReadOnlySpan<byte> utf8Value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            CopyLoop(utf8Value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs
new file mode 100644 (file)
index 0000000..d32224d
--- /dev/null
@@ -0,0 +1,69 @@
+// 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;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="int"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="int"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
+        /// </remarks>
+        public void WriteNumberValue(int value)
+            => WriteNumberValue((long)value);
+
+        /// <summary>
+        /// Writes the <see cref="long"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="long"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
+        /// </remarks>
+        public void WriteNumberValue(long value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberValueIndented(value);
+            }
+            else
+            {
+                WriteNumberValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberValueMinimized(long value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueIndented(long value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs
new file mode 100644 (file)
index 0000000..e9513bb
--- /dev/null
@@ -0,0 +1,246 @@
+// 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.Diagnostics;
+
+namespace System.Text.Json
+{
+    public ref partial struct 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>
+        /// <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);
+
+        /// <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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateValue(value);
+
+            if (escape)
+            {
+                WriteStringEscape(value);
+            }
+            else
+            {
+                WriteStringByOptions(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<char> value)
+        {
+            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
+
+            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
+
+            if (valueIdx != -1)
+            {
+                WriteStringEscapeValue(value, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<char> value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(value);
+            }
+            else
+            {
+                WriteStringMinimized(value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<char> escapedValue)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<char> escapedValue)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length);
+            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length);
+
+            char[] valueArray = null;
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+
+            WriteStringByOptions(escapedValue.Slice(0, written));
+
+            if (valueArray != null)
+            {
+                ArrayPool<char>.Shared.Return(valueArray);
+            }
+        }
+
+        /// <summary>
+        /// 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>
+        /// <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)
+        {
+            JsonWriterHelper.ValidateValue(utf8Value);
+
+            if (escape)
+            {
+                WriteStringEscape(utf8Value);
+            }
+            else
+            {
+                WriteStringByOptions(utf8Value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.String;
+        }
+
+        private void WriteStringEscape(ReadOnlySpan<byte> utf8Value)
+        {
+            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
+
+            Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2);
+
+            if (valueIdx != -1)
+            {
+                WriteStringEscapeValue(utf8Value, valueIdx);
+            }
+            else
+            {
+                WriteStringByOptions(utf8Value);
+            }
+        }
+
+        private void WriteStringByOptions(ReadOnlySpan<byte> utf8Value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteStringIndented(utf8Value);
+            }
+            else
+            {
+                WriteStringMinimized(utf8Value);
+            }
+        }
+
+        private void WriteStringMinimized(ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringIndented(ReadOnlySpan<byte> escapedValue)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteStringValue(escapedValue, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteStringEscapeValue(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
+
+            byte[] valueArray = null;
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written);
+
+            WriteStringByOptions(escapedValue.Slice(0, written));
+
+            if (valueArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(valueArray);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs
new file mode 100644 (file)
index 0000000..107bc0a
--- /dev/null
@@ -0,0 +1,71 @@
+// 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;
+
+namespace System.Text.Json
+{
+    public ref partial struct Utf8JsonWriter
+    {
+        /// <summary>
+        /// Writes the <see cref="uint"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="uint"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
+        /// </remarks>
+        [CLSCompliant(false)]
+        public void WriteNumberValue(uint value)
+            => WriteNumberValue((ulong)value);
+
+        /// <summary>
+        /// Writes the <see cref="ulong"/> value (as a JSON number) as an element of a JSON array.
+        /// </summary>
+        /// <param name="value">The value to be written as a JSON number as an element of a JSON array.</param>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        /// <remarks>
+        /// Writes the <see cref="ulong"/> using the default <see cref="StandardFormat"/> (i.e. 'G'), for example: 32767.
+        /// </remarks>
+        [CLSCompliant(false)]
+        public void WriteNumberValue(ulong value)
+        {
+            ValidateWritingValue();
+            if (_writerOptions.Indented)
+            {
+                WriteNumberValueIndented(value);
+            }
+            else
+            {
+                WriteNumberValueMinimized(value);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            _tokenType = JsonTokenType.Number;
+        }
+
+        private void WriteNumberValueMinimized(ulong value)
+        {
+            int idx = 0;
+            WriteListSeparator(ref idx);
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+
+        private void WriteNumberValueIndented(ulong value)
+        {
+            int idx = WriteCommaAndFormattingPreamble();
+
+            WriteNumberValueFormatLoop(value, ref idx);
+
+            Advance(idx);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs
new file mode 100644 (file)
index 0000000..65763c8
--- /dev/null
@@ -0,0 +1,850 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Provides a high-performance API for forward-only, non-cached writing of UTF-8 encoded JSON text.
+    /// 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>
+    /// 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.
+    /// 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.
+    /// </remarks>
+    public ref partial struct Utf8JsonWriter
+    {
+        private const int StackallocThreshold = 256;
+        private const int DefaultGrowthSize = 4096;
+
+        private readonly IBufferWriter<byte> _output;
+        private int _buffered;
+        private Span<byte> _buffer;
+
+        /// <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;
+            }
+        }
+
+        /// <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 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.
+        // if (_currentDepth >> 31) == 1, add a list separator before writing the item
+        // else, no list separator is needed since we are writing the first item.
+        private int _currentDepth;
+
+        private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent;
+
+        /// <summary>
+        /// Tracks the recursive depth of the nested objects / arrays within the JSON text
+        /// written so far. This provides the depth of the current token.
+        /// </summary>
+        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.
+        /// </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{T}" />.      
+        /// 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.        
+        /// </exception>
+        /// <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"/>.
+        /// </remarks>
+        public JsonWriterState GetCurrentState()
+        {
+            if (_buffered != 0)
+            {
+                throw ThrowHelper.GetInvalidOperationException_CallFlushFirst(_buffered);
+            }
+            return new JsonWriterState
+            {
+                _bytesWritten = BytesWritten,
+                _bytesCommitted = BytesCommitted,
+                _inObject = _inObject,
+                _isNotPrimitive = _isNotPrimitive,
+                _tokenType = _tokenType,
+                _currentDepth = _currentDepth,
+                _writerOptions = _writerOptions,
+                _bitStack = _bitStack,
+            };
+        }
+
+        /// <summary>
+        /// Constructs a new <see cref="Utf8JsonWriter"/> instance with a specified <paramref name="bufferWriter"/>.
+        /// </summary>
+        /// <param name="bufferWriter">An instance of <see cref="IBufferWriter{T}" /> 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>
+        /// <exception cref="ArgumentNullException">
+        /// Thrown when the instance of <see cref="IBufferWriter{T}" /> 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)
+        {
+            _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter));
+            _buffered = 0;
+            BytesCommitted = 0;
+            _buffer = _output.GetSpan();
+
+            _inObject = state._inObject;
+            _isNotPrimitive = state._isNotPrimitive;
+            _tokenType = state._tokenType;
+            _writerOptions = state._writerOptions;
+            _bitStack = state._bitStack;
+
+            _currentDepth = state._currentDepth;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void Advance(int count)
+        {
+            Debug.Assert(count >= 0 && _buffered <= int.MaxValue - count);
+
+            _buffered += count;
+            _buffer = _buffer.Slice(count);
+        }
+
+        /// <summary>
+        /// Advances the underlying <see cref="IBufferWriter{T}" /> based on what has been written so far.
+        /// </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)
+        {
+            if (isFinalBlock && !_writerOptions.SkipValidation && (CurrentDepth != 0 || _tokenType == JsonTokenType.None))
+                ThrowHelper.ThrowInvalidOperationException_DepthNonZeroOrEmptyJson(_currentDepth);
+
+            Flush();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void Flush()
+        {
+            _output.Advance(_buffered);
+            BytesCommitted += _buffered;
+            _buffered = 0;
+        }
+
+        /// <summary>
+        /// Writes the beginning of a JSON array.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// 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()
+        {
+            WriteStart(JsonConstants.OpenBracket);
+            _tokenType = JsonTokenType.StartArray;
+        }
+
+        /// <summary>
+        /// Writes the beginning of a JSON object.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// 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()
+        {
+            WriteStart(JsonConstants.OpenBrace);
+            _tokenType = JsonTokenType.StartObject;
+        }
+
+        private void WriteStart(byte token)
+        {
+            if (CurrentDepth >= JsonConstants.MaxWriterDepth)
+                ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth);
+
+            if (_writerOptions.IndentedOrNotSkipValidation)
+            {
+                WriteStartSlow(token);
+            }
+            else
+            {
+                WriteStartMinimized(token);
+            }
+
+            _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            _currentDepth++;
+            _isNotPrimitive = true;
+        }
+
+        private void WriteStartMinimized(byte token)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = token;
+
+            Advance(idx);
+        }
+
+        private void WriteStartSlow(byte token)
+        {
+            Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation);
+
+            if (_writerOptions.Indented)
+            {
+                if (!_writerOptions.SkipValidation)
+                {
+                    ValidateStart();
+                    UpdateBitStackOnStart(token);
+                }
+                WriteStartIndented(token);
+            }
+            else
+            {
+                Debug.Assert(!_writerOptions.SkipValidation);
+                ValidateStart();
+                UpdateBitStackOnStart(token);
+                WriteStartMinimized(token);
+            }
+        }
+
+        private void ValidateStart()
+        {
+            if (_inObject)
+            {
+                Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
+                ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotStartObjectArrayWithoutProperty, tokenType: _tokenType);
+            }
+            else
+            {
+                Debug.Assert(_tokenType != JsonTokenType.StartObject);
+                if (_tokenType != JsonTokenType.None && (!_isNotPrimitive || CurrentDepth == 0))
+                {
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotStartObjectArrayAfterPrimitiveOrClose, tokenType: _tokenType);
+                }
+            }
+        }
+
+        private void WriteStartIndented(byte token)
+        {
+            int idx = 0;
+            if (_currentDepth < 0)
+            {
+                if (_buffer.Length <= idx)
+                {
+                    GrowAndEnsure();
+                }
+                _buffer[idx++] = JsonConstants.ListSeparator;
+            }
+
+            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);
+            }
+
+            if (_buffer.Length <= idx)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = token;
+
+            Advance(idx);
+        }
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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)
+        {
+            ValidatePropertyNameAndDepth(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket);
+            }
+            else
+            {
+                WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBracket);
+            }
+
+            _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            _currentDepth++;
+            _isNotPrimitive = true;
+            _tokenType = JsonTokenType.StartArray;
+        }
+
+        /// <summary>
+        /// 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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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)
+        {
+            ValidatePropertyNameAndDepth(utf8PropertyName);
+
+            if (escape)
+            {
+                WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace);
+            }
+            else
+            {
+                WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBrace);
+            }
+
+            _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            _currentDepth++;
+            _isNotPrimitive = true;
+            _tokenType = JsonTokenType.StartObject;
+        }
+
+        private void WriteStartEscape(ReadOnlySpan<byte> utf8PropertyName, byte token)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStartEscapeProperty(utf8PropertyName, token, propertyIdx);
+            }
+            else
+            {
+                WriteStartByOptions(utf8PropertyName, token);
+            }
+        }
+
+        private void WriteStartByOptions(ReadOnlySpan<byte> utf8PropertyName, byte token)
+        {
+            ValidateWritingProperty(token);
+            int idx;
+            if (_writerOptions.Indented)
+            {
+                idx = WritePropertyNameIndented(utf8PropertyName);
+            }
+            else
+            {
+                idx = WritePropertyNameMinimized(utf8PropertyName);
+            }
+
+            if (1 > _buffer.Length - idx)
+            {
+                AdvanceAndGrow(ref idx, 1);
+            }
+
+            _buffer[idx++] = token;
+
+            Advance(idx);
+        }
+
+        private void WriteStartEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, byte token, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+            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);
+                }
+            }
+
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStartByOptions(escapedPropertyName.Slice(0, written), token);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<byte>.Shared.Return(propertyArray);
+            }
+        }
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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);
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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);
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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)
+        {
+            ValidatePropertyNameAndDepth(propertyName);
+
+            if (escape)
+            {
+                WriteStartEscape(propertyName, JsonConstants.OpenBracket);
+            }
+            else
+            {
+                WriteStartByOptions(propertyName, JsonConstants.OpenBracket);
+            }
+
+            _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            _currentDepth++;
+            _isNotPrimitive = true;
+            _tokenType = JsonTokenType.StartArray;
+        }
+
+        /// <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>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the specified property name is too large.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// 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)
+        {
+            ValidatePropertyNameAndDepth(propertyName);
+
+            if (escape)
+            {
+                WriteStartEscape(propertyName, JsonConstants.OpenBrace);
+            }
+            else
+            {
+                WriteStartByOptions(propertyName, JsonConstants.OpenBrace);
+            }
+
+            _currentDepth &= JsonConstants.RemoveFlagsBitMask;
+            _currentDepth++;
+            _isNotPrimitive = true;
+            _tokenType = JsonTokenType.StartObject;
+        }
+
+        private void WriteStartEscape(ReadOnlySpan<char> propertyName, byte token)
+        {
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+            Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2);
+
+            if (propertyIdx != -1)
+            {
+                WriteStartEscapeProperty(propertyName, token, propertyIdx);
+            }
+            else
+            {
+                WriteStartByOptions(propertyName, token);
+            }
+        }
+
+        private void WriteStartByOptions(ReadOnlySpan<char> propertyName, byte token)
+        {
+            ValidateWritingProperty(token);
+            int idx;
+            if (_writerOptions.Indented)
+            {
+                idx = WritePropertyNameIndented(propertyName);
+            }
+            else
+            {
+                idx = WritePropertyNameMinimized(propertyName);
+            }
+
+            if (1 > _buffer.Length - idx)
+            {
+                AdvanceAndGrow(ref idx, 1);
+            }
+
+            _buffer[idx++] = token;
+
+            Advance(idx);
+        }
+
+        private void WriteStartEscapeProperty(ReadOnlySpan<char> propertyName, byte token, int firstEscapeIndexProp)
+        {
+            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+            Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+            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);
+                }
+            }
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+            WriteStartByOptions(escapedPropertyName.Slice(0, written), token);
+
+            if (propertyArray != null)
+            {
+                ArrayPool<char>.Shared.Return(propertyArray);
+            }
+        }
+
+        /// <summary>
+        /// Writes the end of a JSON array.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        public void WriteEndArray()
+        {
+            WriteEnd(JsonConstants.CloseBracket);
+            _tokenType = JsonTokenType.EndArray;
+        }
+
+        /// <summary>
+        /// Writes the end of a JSON object.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+        /// </exception>
+        public void WriteEndObject()
+        {
+            WriteEnd(JsonConstants.CloseBrace);
+            _tokenType = JsonTokenType.EndObject;
+        }
+
+        private void WriteEnd(byte token)
+        {
+            if (_writerOptions.IndentedOrNotSkipValidation)
+            {
+                WriteEndSlow(token);
+            }
+            else
+            {
+                WriteEndMinimized(token);
+            }
+
+            SetFlagToAddListSeparatorBeforeNextItem();
+            // Necessary if WriteEndX is called without a corresponding WriteStartX first.
+            if (CurrentDepth != 0)
+            {
+                _currentDepth--;
+            }
+        }
+
+        private void WriteEndMinimized(byte token)
+        {
+            if (_buffer.Length < 1)
+            {
+                GrowAndEnsure();
+            }
+
+            _buffer[0] = token;
+            Advance(1);
+        }
+
+        private void WriteEndSlow(byte token)
+        {
+            Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation);
+
+            if (_writerOptions.Indented)
+            {
+                if (!_writerOptions.SkipValidation)
+                {
+                    ValidateEnd(token);
+                }
+                WriteEndIndented(token);
+            }
+            else
+            {
+                Debug.Assert(!_writerOptions.SkipValidation);
+                ValidateEnd(token);
+                WriteEndMinimized(token);
+            }
+        }
+
+        private void ValidateEnd(byte token)
+        {
+            if (_bitStack.CurrentDepth <= 0)
+                ThrowHelper.ThrowInvalidOperationException(ExceptionResource.MismatchedObjectArray, token);
+
+            if (token == JsonConstants.CloseBracket)
+            {
+                if (_inObject)
+                {
+                    Debug.Assert(_tokenType != JsonTokenType.None);
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.MismatchedObjectArray, token);
+                }
+            }
+            else
+            {
+                Debug.Assert(token == JsonConstants.CloseBrace);
+
+                if (!_inObject)
+                {
+                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.MismatchedObjectArray, token);
+                }
+            }
+
+            _inObject = _bitStack.Pop();
+        }
+
+        private void WriteEndIndented(byte token)
+        {
+            // Do not format/indent empty JSON object/array.
+            if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
+            {
+                WriteEndMinimized(token);
+            }
+            else
+            {
+                int idx = 0;
+                WriteNewLine(ref idx);
+
+                int indent = Indentation;
+                // Necessary if WriteEndX is called without a corresponding WriteStartX first.
+                if (indent != 0)
+                {
+                    // The end token should be at an outer indent and since we haven't updated
+                    // 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)
+                {
+                    AdvanceAndGrow(ref idx);
+                }
+                _buffer[idx++] = token;
+
+                Advance(idx);
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void WriteNewLine(ref int idx)
+        {
+            // 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)
+            {
+                AdvanceAndGrow(ref idx);
+            }
+            _buffer[idx++] = JsonConstants.LineFeed;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void UpdateBitStackOnStart(byte token)
+        {
+            if (token == JsonConstants.OpenBracket)
+            {
+                _bitStack.PushFalse();
+                _inObject = false;
+            }
+            else
+            {
+                Debug.Assert(token == JsonConstants.OpenBrace);
+                _bitStack.PushTrue();
+                _inObject = true;
+            }
+        }
+
+        private void GrowAndEnsure()
+        {
+            Flush();
+            int previousSpanLength = _buffer.Length;
+            Debug.Assert(previousSpanLength < DefaultGrowthSize);
+            _buffer = _output.GetSpan(DefaultGrowthSize);
+            if (_buffer.Length <= previousSpanLength)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetLargerSpan);
+            }
+        }
+
+        private void GrowAndEnsure(int minimumSize)
+        {
+            Flush();
+            Debug.Assert(minimumSize < DefaultGrowthSize);
+            _buffer = _output.GetSpan(DefaultGrowthSize);
+            if (_buffer.Length < minimumSize)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetMinimumSizeSpan, minimumSize);
+            }
+        }
+
+        private void AdvanceAndGrow(ref int alreadyWritten)
+        {
+            Debug.Assert(alreadyWritten >= 0);
+            Advance(alreadyWritten);
+            GrowAndEnsure();
+            alreadyWritten = 0;
+        }
+
+        private void AdvanceAndGrow(ref int alreadyWritten, int minimumSize)
+        {
+            Debug.Assert(minimumSize >= 1 && minimumSize <= 128);
+            Advance(alreadyWritten);
+            GrowAndEnsure(minimumSize);
+            alreadyWritten = 0;
+        }
+
+        private void CopyLoop(ReadOnlySpan<byte> span, ref int idx)
+        {
+            while (true)
+            {
+                if (span.Length <= _buffer.Length - idx)
+                {
+                    span.CopyTo(_buffer.Slice(idx));
+                    idx += span.Length;
+                    break;
+                }
+
+                span.Slice(0, _buffer.Length - idx).CopyTo(_buffer.Slice(idx));
+                span = span.Slice(_buffer.Length - idx);
+                idx = _buffer.Length;
+                AdvanceAndGrow(ref idx);
+            }
+        }
+
+        private void SetFlagToAddListSeparatorBeforeNextItem()
+        {
+            _currentDepth |= 1 << 31;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs b/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs
new file mode 100644 (file)
index 0000000..13249f8
--- /dev/null
@@ -0,0 +1,86 @@
+// 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 c439147..41cff4a 100644 (file)
@@ -2,14 +2,13 @@
 // 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.Text.Json;
 using Xunit;
 
-namespace System.Text.JsonTests
+namespace System.Text.Json.Tests
 {
     public static partial class BitStackTests
     {
-        private static Random s_random = new Random(42);
+        private static readonly Random s_random = new Random(42);
 
         [Fact]
         public static void DefaultBitStack()
diff --git a/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs b/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs
new file mode 100644 (file)
index 0000000..22f29ad
--- /dev/null
@@ -0,0 +1,44 @@
+// 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 FixedSizedBufferWriter : IBufferWriter<byte>
+    {
+        private readonly byte[] _buffer;
+        private int _count;
+
+        public FixedSizedBufferWriter(int capacity)
+        {
+            _buffer = new byte[capacity];
+        }
+
+        public void Clear()
+        {
+            _count = 0;
+        }
+
+        public Span<byte> Free => _buffer.AsSpan(_count);
+
+        public Span<byte> Formatted => _buffer.AsSpan(0, _count);
+
+        public Memory<byte> GetMemory(int minimumLength = 0) => _buffer.AsMemory(_count);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<byte> GetSpan(int minimumLength = 0) => _buffer.AsSpan(_count);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Advance(int bytes)
+        {
+            _count += bytes;
+            if (_count > _buffer.Length)
+            {
+                throw new InvalidOperationException("Cannot advance past the end of the buffer.");
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/InvalidBufferWriter.cs b/src/libraries/System.Text.Json/tests/InvalidBufferWriter.cs
new file mode 100644 (file)
index 0000000..2a92ccb
--- /dev/null
@@ -0,0 +1,26 @@
+// 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 InvalidBufferWriter : IBufferWriter<byte>
+    {
+        public InvalidBufferWriter()
+        {
+        }
+
+        public Memory<byte> GetMemory(int minimumLength = 0) => new byte[10];
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<byte> GetSpan(int minimumLength = 0) => new byte[10];
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Advance(int bytes)
+        {
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs b/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs
new file mode 100644 (file)
index 0000000..87a1670
--- /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 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
new file mode 100644 (file)
index 0000000..b02d6b4
--- /dev/null
@@ -0,0 +1,43 @@
+// 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 3a181ed..7ca7174 100644 (file)
@@ -4,18 +4,24 @@
     <Configurations>netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release</Configurations>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="ArrayBufferWriter.cs" />
     <Compile Include="BitStackTests.cs" />
     <Compile Include="BufferFactory.cs" />
     <Compile Include="BufferSegment.cs" />
+    <Compile Include="FixedSizedBufferWriter.cs" />
+    <Compile Include="InvalidBufferWriter.cs" />
     <Compile Include="JsonReaderStateTests.cs" />
     <Compile Include="JsonTestHelper.cs" />
+    <Compile Include="JsonWriterStateTests.cs" />
+    <Compile Include="ResizableArray.cs" />
     <Compile Include="TestCaseType.cs" />
     <Compile Include="Utf8JsonReaderTests.cs" />
     <Compile Include="Utf8JsonReaderTests.MultiSegment.cs" />
     <Compile Include="Utf8JsonReaderTests.TryGet.cs" />
+    <Compile Include="Utf8JsonWriterTests.cs" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="..\src\System\Text\Json\BitStack.cs" Link="BitStack.cs"/>
+    <Compile Include="..\src\System\Text\Json\BitStack.cs" Link="BitStack.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Collections" />
diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs
new file mode 100644 (file)
index 0000000..43e78ea
--- /dev/null
@@ -0,0 +1,3491 @@
+// 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;
+using System.IO;
+using Newtonsoft.Json;
+using System.Globalization;
+
+namespace System.Text.Json.Tests
+{
+    public class Utf8JsonWriterTests
+    {
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void NullCtor(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(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) { }
+
+            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 FlushEmpty(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(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) { }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void FlushMultipleTimes(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+            var output = new FixedSizedBufferWriter(10);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            jsonUtf8.Flush();
+            Assert.Equal(2, jsonUtf8.BytesCommitted);
+            jsonUtf8.Flush();
+            Assert.Equal(2, jsonUtf8.BytesCommitted);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidBufferWriter(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            var output = new InvalidBufferWriter();
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                jsonUtf8.WriteNumberValue((ulong)12345678901);
+                Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
+            }
+            catch (ArgumentException) { }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            var output = new FixedSizedBufferWriter(37);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            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) { }
+
+            output = new FixedSizedBufferWriter(39);
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            jsonUtf8.WriteStringValue(guid);
+            jsonUtf8.Flush();
+            string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+            Assert.Equal(38, output.Formatted.Length);
+            Assert.Equal($"\"{guid.ToString()}\"", actualStr);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            var output = new FixedSizedBufferWriter(28);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            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) { }
+
+            output = new FixedSizedBufferWriter(29);
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            jsonUtf8.WriteStringValue(date);
+            jsonUtf8.Flush();
+            string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+            Assert.Equal(29, output.Formatted.Length);
+            Assert.Equal($"\"{date.ToString("O")}\"", actualStr);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            var output = new FixedSizedBufferWriter(34);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            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) { }
+
+            output = new FixedSizedBufferWriter(35);
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            jsonUtf8.WriteStringValue(date);
+            jsonUtf8.Flush();
+            string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+            Assert.Equal(35, output.Formatted.Length);
+            Assert.Equal($"\"{date.ToString("O")}\"", actualStr);
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation)
+        {
+            var random = new Random(42);
+
+            for (int i = 0; i < 1_000; i++)
+            {
+                var output = new FixedSizedBufferWriter(31);
+                decimal value = JsonTestHelper.NextDecimal(random, 78E14, -78E14);
+                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                jsonUtf8.WriteNumberValue(value);
+
+                jsonUtf8.Flush();
+                string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+                Assert.True(output.Formatted.Length <= 31);
+                Assert.Equal(decimal.Parse(actualStr, CultureInfo.InvariantCulture), value);
+            }
+
+            for (int i = 0; i < 1_000; i++)
+            {
+                var output = new FixedSizedBufferWriter(31);
+                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);
+                jsonUtf8.WriteNumberValue(value);
+
+                jsonUtf8.Flush();
+                string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+                Assert.True(output.Formatted.Length <= 31);
+                Assert.Equal(decimal.Parse(actualStr, CultureInfo.InvariantCulture), value);
+            }
+
+            {
+                var output = new FixedSizedBufferWriter(31);
+                decimal value = 9999999999999999999999999999m;
+                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                jsonUtf8.WriteNumberValue(value);
+
+                jsonUtf8.Flush();
+                string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+                Assert.Equal(value.ToString().Length, output.Formatted.Length);
+                Assert.Equal(decimal.Parse(actualStr, CultureInfo.InvariantCulture), value);
+            }
+
+            {
+                var output = new FixedSizedBufferWriter(31);
+                decimal value = -9999999999999999999999999999m;
+                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+                jsonUtf8.WriteNumberValue(value);
+
+                jsonUtf8.Flush();
+                string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+                Assert.Equal(value.ToString().Length, output.Formatted.Length);
+                Assert.Equal(decimal.Parse(actualStr, CultureInfo.InvariantCulture), value);
+            }
+
+            {
+                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);
+
+                try
+                {
+                    jsonUtf8.WriteNumberValue(value);
+                    Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space.");
+                }
+                catch (ArgumentException) { }
+
+                output = new FixedSizedBufferWriter(31);
+                jsonUtf8 = new Utf8JsonWriter(output, state);
+                jsonUtf8.WriteNumberValue(value);
+
+                jsonUtf8.Flush();
+                string actualStr = Encoding.UTF8.GetString(output.Formatted);
+
+                Assert.Equal(value.ToString().Length, output.Formatted.Length);
+                Assert.Equal(decimal.Parse(actualStr, CultureInfo.InvariantCulture), value);
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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 jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray("property at start", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject("property at start", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartArray("property inside array", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStringValue("key");
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteString("key", "value");
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteEndArray();
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartObject("some object", escape: false);
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartObject("some object", escape: false);
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartArray("test array", escape: false);
+                jsonUtf8.WriteEndArray();
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteEndArray();
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartObject("test object", escape: false);
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidJsonIncomplete(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.WriteStartArray();
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteEndArray();
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartObject("some object", escape: false);
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStartObject("some object", escape: false);
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartArray("test array", escape: false);
+                jsonUtf8.WriteEndArray();
+                jsonUtf8.Flush(isFinalBlock: true);
+                WriterDidNotThrow(skipValidation);
+            }
+            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.WriteNumberValue(12345);
+                jsonUtf8.WriteNumberValue(12345);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteStartArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteStartObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteStartArray("property name", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteStartObject("property name", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteString("property name", "value", escape: false);
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteEndArray();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(12345);
+                jsonUtf8.WriteEndObject();
+                WriterDidNotThrow(skipValidation);
+            }
+            catch (InvalidOperationException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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 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) { }
+
+            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, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(float.PositiveInfinity);
+                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
+            }
+            catch (ArgumentException) { }
+
+            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, state);
+            try
+            {
+                jsonUtf8.WriteNumberValue(float.NaN);
+                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
+            }
+            catch (ArgumentException) { }
+
+            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, 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, 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, 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, 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, state);
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteNumber("name", float.NaN);
+                Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values.");
+            }
+            catch (ArgumentException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void InvalidJsonContinueShouldSucceed(bool formatted)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
+
+            var output = new ArrayBufferWriter(1024);
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            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++)
+            {
+                if (formatted)
+                    sb.Append(Environment.NewLine);
+                sb.Append("]");
+            }
+            sb.Append(",");
+            if (formatted)
+                sb.Append(Environment.NewLine);
+            sb.Append("[]");
+
+            Assert.Equal(sb.ToString(), actualStr);
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WritingTooDeep(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
+            {
+                for (int i = 0; i < 1001; i++)
+                {
+                    jsonUtf8.WriteStartArray();
+                }
+                Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000.");
+            }
+            catch (InvalidOperationException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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 jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                for (int i = 0; i < 1000; i++)
+                {
+                    jsonUtf8.WriteStartArray("name");
+                }
+                Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000.");
+            }
+            catch (InvalidOperationException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                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.");
+            }
+            catch (InvalidOperationException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [OuterLoop]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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 = new byte[1_000_000_000];
+            key.Fill((byte)'a');
+
+            var keyChars = new char[1_000_000_000];
+            keyChars.AsSpan().Fill('a');
+
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartArray(keyChars);
+                Assert.True(false, "Expected ArgumentException to be thrown for depth >= 1000.");
+            }
+            catch (ArgumentException) { }
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteStartArray(key);
+                Assert.True(false, "Expected ArgumentException to be thrown for depth >= 1000.");
+            }
+            catch (ArgumentException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteSingleValue(bool formatted, bool skipValidation)
+        {
+            string expectedStr = "123456789012345";
+
+            var state = new JsonWriterState(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();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteHelloWorld(bool formatted, bool skipValidation)
+        {
+            string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted);
+
+            var state = new JsonWriterState(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);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString("message", "Hello, World!", escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteString("message", "Hello, World!".AsSpan(), escape: false);
+                        break;
+                    case 2:
+                        jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        break;
+                    case 3:
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!", escape: false);
+                        break;
+                    case 4:
+                        jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan(), escape: false);
+                        break;
+                    case 5:
+                        jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        break;
+                    case 6:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!", escape: false);
+                        break;
+                    case 7:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan(), escape: false);
+                        break;
+                    case 8:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"), escape: false);
+                        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}");
+
+                output.Dispose();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WritePartialHelloWorld(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();
+
+            Assert.Equal(0, jsonUtf8.BytesCommitted);
+            Assert.Equal(1, jsonUtf8.BytesWritten);
+
+            jsonUtf8.WriteString("message", "Hello, World!");
+
+            Assert.Equal(16, jsonUtf8.BytesCommitted);
+            if (formatted)
+                Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesWritten);
+            else
+                Assert.Equal(26, jsonUtf8.BytesWritten);
+
+            jsonUtf8.Flush(isFinalBlock: false);
+
+            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);
+
+            jsonUtf8.WriteString("message", "Hello, World!");
+            jsonUtf8.WriteEndObject();
+
+            if (formatted)
+                Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted);
+            else
+                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
+            else
+                Assert.Equal(53, jsonUtf8.BytesWritten);
+
+            jsonUtf8.Flush(isFinalBlock: true);
+
+            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();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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);
+
+            jsonUtf8.WriteStartObject();
+
+            Assert.Equal(0, state.BytesCommitted);
+            Assert.Equal(0, state.BytesWritten);
+
+            jsonUtf8.Flush(isFinalBlock: false);
+
+            state = jsonUtf8.GetCurrentState();
+
+            Assert.Equal(1, state.BytesCommitted);
+            Assert.Equal(1, state.BytesWritten);
+
+            jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                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
+            {
+                jsonUtf8.Flush(isFinalBlock: false);
+                state = jsonUtf8.GetCurrentState();
+            }
+
+            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]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        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);
+
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush();
+
+                Assert.Equal(0, jsonUtf8.CurrentDepth);
+                
+                state = jsonUtf8.GetCurrentState();
+
+                jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                try
+                {
+                    jsonUtf8.WriteStartObject();
+                    WriterDidNotThrow(skipValidation);
+                }
+                catch (InvalidOperationException) { }
+
+                output.Dispose();
+            }
+
+            {
+                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.WriteEndObject();
+                jsonUtf8.Flush();
+
+                Assert.Equal(0, jsonUtf8.CurrentDepth);
+                state = jsonUtf8.GetCurrentState();
+
+                jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                try
+                {
+                    jsonUtf8.WriteStartObject("name");
+                    WriterDidNotThrow(skipValidation);
+                }
+                catch (InvalidOperationException) { }
+
+                output.Dispose();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "comment")]
+        [InlineData(true, false, "comment")]
+        [InlineData(false, true, "comment")]
+        [InlineData(false, false, "comment")]
+        [InlineData(true, true, "comm><ent")]
+        [InlineData(true, false, "comm><ent")]
+        [InlineData(false, true, "comm><ent")]
+        [InlineData(false, false, "comm><ent")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        public void WriteComments(bool formatted, bool skipValidation, string comment)
+        {
+            string expectedStr = GetCommentExpectedString(prettyPrint: formatted, comment);
+
+            var state = new JsonWriterState(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.WriteStartArray();
+
+                for (int j = 0; j < 10; j++)
+                {
+                    WriteCommentValue(ref jsonUtf8, i, comment);
+                }
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStringValue(comment);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStringValue(comment.AsSpan());
+                        break;
+                    case 2:
+                        jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(comment));
+                        break;
+                }
+
+                WriteCommentValue(ref 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();
+            }
+        }
+
+        private static void WriteCommentValue(ref Utf8JsonWriter jsonUtf8, int i, string comment)
+        {
+            switch (i)
+            {
+                case 0:
+                    jsonUtf8.WriteCommentValue(comment, escape: false);
+                    break;
+                case 1:
+                    jsonUtf8.WriteCommentValue(comment.AsSpan(), escape: false);
+                    break;
+                case 2:
+                    jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment), escape: false);
+                    break;
+            }
+        }
+
+        [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 });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartArray();
+
+                for (int j = 0; j < 10; j++)
+                {
+                    switch (i)
+                    {
+                        case 0:
+                            jsonUtf8.WriteStringValue(value);
+                            break;
+                        case 1:
+                            jsonUtf8.WriteStringValue(value.AsSpan());
+                            break;
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "mess\nage", "Hello, \nWorld!")]
+        [InlineData(true, false, "mess\nage", "Hello, \nWorld!")]
+        [InlineData(false, true, "mess\nage", "Hello, \nWorld!")]
+        [InlineData(false, false, "mess\nage", "Hello, \nWorld!")]
+        [InlineData(true, true, "message", "Hello, \nWorld!")]
+        [InlineData(true, false, "message", "Hello, \nWorld!")]
+        [InlineData(false, true, "message", "Hello, \nWorld!")]
+        [InlineData(false, false, "message", "Hello, \nWorld!")]
+        [InlineData(true, true, "mess\nage", "Hello, World!")]
+        [InlineData(true, false, "mess\nage", "Hello, World!")]
+        [InlineData(false, true, "mess\nage", "Hello, World!")]
+        [InlineData(false, false, "mess\nage", "Hello, World!")]
+        [InlineData(true, true, "message", "Hello, World!")]
+        [InlineData(true, false, "message", "Hello, World!")]
+        [InlineData(false, true, "message", "Hello, World!")]
+        [InlineData(false, false, "message", "Hello, World!")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")]
+        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 });
+
+            for (int i = 0; i < 18; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(key, value, escape: true);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: true);
+                        break;
+                    case 2:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: true);
+                        break;
+                    case 3:
+                        jsonUtf8.WriteString(key, value.AsSpan(), escape: true);
+                        break;
+                    case 4:
+                        jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: true);
+                        break;
+                    case 5:
+                        jsonUtf8.WriteString(key.AsSpan(), value, escape: true);
+                        break;
+                    case 6:
+                        jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: true);
+                        break;
+                    case 7:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: true);
+                        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);
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void EscapeAsciiCharacters(bool formatted, bool skipValidation)
+        {
+            var propertyArray = new char[128];
+
+            char[] specialCases = { '+', '`', (char)0x7F };
+            for (int i = 0; i < propertyArray.Length; i++)
+            {
+                if (Array.IndexOf(specialCases, (char)i) != -1)
+                {
+                    propertyArray[i] = (char)0;
+                }
+                else
+                {
+                    propertyArray[i] = (char)i;
+                }
+            }
+
+            string propertyName = new string(propertyArray);
+            string value = new string(propertyArray);
+
+            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 output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        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);
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void EscapeCharacters(bool formatted, bool skipValidation)
+        {
+            // Do not include surrogate pairs.
+            var propertyArray = new char[0xD800 + (0xFFFF - 0xE000) + 1];
+
+            for (int i = 128; i < propertyArray.Length; i++)
+            {
+                if (i < 0xD800 || i > 0xDFFF)
+                {
+                    propertyArray[i] = (char)i;
+                }
+            }
+
+            string propertyName = new string(propertyArray);
+            string value = new string(propertyArray);
+
+            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 output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        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);
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void EscapeSurrogatePairs(bool formatted, bool skipValidation)
+        {
+            var propertyArray = new char[10] { 'a', (char)0xD800, (char)0xDC00, (char)0xD803, (char)0xDE6D, (char)0xD834, (char)0xDD1E, (char)0xDBFF, (char)0xDFFF, 'a' };
+
+            string propertyName = new string(propertyArray);
+            string value = new string(propertyArray);
+
+            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 output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(propertyName, value, escape: true);
+                        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);
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void InvalidUTF8(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);
+
+            jsonUtf8.WriteStartObject();
+            for (int i = 0; i < 8; i++)
+            {
+                try
+                {
+                    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;
+                    }
+                }
+                catch (ArgumentException) { }
+            }
+            jsonUtf8.WriteEndObject();
+            jsonUtf8.Flush();
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [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);
+
+            jsonUtf8.WriteStartObject();
+
+            for (int i = 0; i < 1_000; i++)
+                jsonUtf8.WriteString("message", "Hello, World!", escape: false);
+
+            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();
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteStartEnd(bool formatted, bool skipValidation)
+        {
+            string expectedStr = GetStartEndExpectedString(prettyPrint: formatted);
+
+            var output = new ArrayBufferWriter(1024);
+
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            jsonUtf8.WriteStartArray();
+            jsonUtf8.WriteStartObject();
+            jsonUtf8.WriteEndObject();
+            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();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void WriteStartEndInvalid(bool formatted)
+        {
+            {
+                string expectedStr = "[}";
+
+                var output = new ArrayBufferWriter(1024);
+
+                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
+
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                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();
+            }
+
+            {
+                string expectedStr = "{]";
+
+                var output = new ArrayBufferWriter(1024);
+
+                var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true });
+
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidation)
+        {
+            string expectedStr = GetStartEndWithPropertyArrayExpectedString(prettyPrint: formatted);
+
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStartArray("property name", escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStartArray("property name".AsSpan(), escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteEndArray();
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, 10)]
+        [InlineData(true, false, 10)]
+        [InlineData(false, true, 10)]
+        [InlineData(false, false, 10)]
+        [InlineData(true, true, 100)]
+        [InlineData(true, false, 100)]
+        [InlineData(false, true, 100)]
+        [InlineData(false, false, 100)]
+        public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidation, int keyLength)
+        {
+            var keyChars = new char[keyLength];
+            for (int i = 0; i < keyChars.Length; i++)
+            {
+                keyChars[i] = '<';
+            }
+            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 });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStartArray(key, escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStartArray(key.AsSpan(), escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteEndArray();
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidation)
+        {
+            string expectedStr = GetStartEndWithPropertyObjectExpectedString(prettyPrint: formatted);
+
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStartObject("property name", escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStartObject("property name".AsSpan(), escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteEndObject();
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, 10)]
+        [InlineData(true, false, 10)]
+        [InlineData(false, true, 10)]
+        [InlineData(false, false, 10)]
+        [InlineData(true, true, 100)]
+        [InlineData(true, false, 100)]
+        [InlineData(false, true, 100)]
+        [InlineData(false, false, 100)]
+        public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidation, int keyLength)
+        {
+            var keyChars = new char[keyLength];
+            for (int i = 0; i < keyChars.Length; i++)
+            {
+                keyChars[i] = '<';
+            }
+            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 });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStartObject(key, escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStartObject(key.AsSpan(), escape: false);
+                        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);
+                        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);
+
+                output.Dispose();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteArrayWithProperty(bool formatted, bool skipValidation)
+        {
+            string expectedStr = GetArrayWithPropertyExpectedString(prettyPrint: formatted);
+
+            var state = new JsonWriterState(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.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteStartArray("message", escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteStartArray("message".AsSpan(), escape: false);
+                        break;
+                    case 2:
+                        jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message"), escape: false);
+                        break;
+                }
+
+                jsonUtf8.WriteEndArray();
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, true, "message")]
+        [InlineData(true, false, true, "message")]
+        [InlineData(false, true, true, "message")]
+        [InlineData(false, false, true, "message")]
+        [InlineData(true, true, true, "mess><age")]
+        [InlineData(true, false, true, "mess><age")]
+        [InlineData(false, true, true, "mess><age")]
+        [InlineData(false, false, true, "mess><age")]
+        [InlineData(true, true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, true, false, "message")]
+        [InlineData(true, false, false, "message")]
+        [InlineData(false, true, false, "message")]
+        [InlineData(false, false, false, "message")]
+        [InlineData(true, true, false, "mess><age")]
+        [InlineData(true, false, false, "mess><age")]
+        [InlineData(false, true, false, "mess><age")]
+        [InlineData(false, false, false, "mess><age")]
+        [InlineData(true, true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        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 });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteBoolean(keyString, value, escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteStartArray("temp");
+                jsonUtf8.WriteBooleanValue(true);
+                jsonUtf8.WriteBooleanValue(true);
+                jsonUtf8.WriteBooleanValue(false);
+                jsonUtf8.WriteBooleanValue(false);
+                jsonUtf8.WriteEndArray();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "message")]
+        [InlineData(true, false, "message")]
+        [InlineData(false, true, "message")]
+        [InlineData(false, false, "message")]
+        [InlineData(true, true, "mess><age")]
+        [InlineData(true, false, "mess><age")]
+        [InlineData(false, true, "mess><age")]
+        [InlineData(false, false, "mess><age")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        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 });
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteNull(keyString, escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteNull(keyString.AsSpan(), escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteStartArray("temp");
+                jsonUtf8.WriteNullValue();
+                jsonUtf8.WriteNullValue();
+                jsonUtf8.WriteEndArray();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, 0)]
+        [InlineData(true, false, 0)]
+        [InlineData(false, true, 0)]
+        [InlineData(false, false, 0)]
+        [InlineData(true, true, -1)]
+        [InlineData(true, false, -1)]
+        [InlineData(false, true, -1)]
+        [InlineData(false, false, -1)]
+        [InlineData(true, true, 1)]
+        [InlineData(true, false, 1)]
+        [InlineData(false, true, 1)]
+        [InlineData(false, false, 1)]
+        [InlineData(true, true, int.MaxValue)]
+        [InlineData(true, false, int.MaxValue)]
+        [InlineData(false, true, int.MaxValue)]
+        [InlineData(false, false, int.MaxValue)]
+        [InlineData(true, true, int.MinValue)]
+        [InlineData(true, false, int.MinValue)]
+        [InlineData(false, true, int.MinValue)]
+        [InlineData(false, false, int.MinValue)]
+        [InlineData(true, true, 12345)]
+        [InlineData(true, false, 12345)]
+        [InlineData(false, true, 12345)]
+        [InlineData(false, false, 12345)]
+        public void WriteIntegerValue(bool formatted, bool skipValidation, int value)
+        {
+            string expectedStr = GetIntegerExpectedString(prettyPrint: formatted, value);
+
+            var state = new JsonWriterState(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.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteNumber("message", value, escape: false);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteNumber("message".AsSpan(), value, escape: false);
+                        break;
+                    case 2:
+                        jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value, escape: false);
+                        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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "message")]
+        [InlineData(true, false, "message")]
+        [InlineData(false, true, "message")]
+        [InlineData(false, false, "message")]
+        [InlineData(true, true, "mess><age")]
+        [InlineData(true, false, "mess><age")]
+        [InlineData(false, true, "mess><age")]
+        [InlineData(false, false, "mess><age")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        public void WriteNumbers(bool formatted, bool skipValidation, string keyString)
+        {
+            var random = new Random(42);
+            const int numberOfItems = 1_000;
+
+            var ints = new int[numberOfItems];
+            ints[0] = 0;
+            ints[1] = int.MaxValue;
+            ints[2] = int.MinValue;
+            ints[3] = 12345;
+            ints[4] = -12345;
+            for (int i = 5; i < numberOfItems; i++)
+            {
+                ints[i] = random.Next(int.MinValue, int.MaxValue);
+            }
+
+            var uints = new uint[numberOfItems];
+            uints[0] = uint.MaxValue;
+            uints[1] = uint.MinValue;
+            uints[2] = 3294967295;
+            for (int i = 3; i < numberOfItems; i++)
+            {
+                uint thirtyBits = (uint)random.Next(1 << 30);
+                uint twoBits = (uint)random.Next(1 << 2);
+                uint fullRange = (thirtyBits << 2) | twoBits;
+                uints[i] = fullRange;
+            }
+
+            var longs = new long[numberOfItems];
+            longs[0] = 0;
+            longs[1] = long.MaxValue;
+            longs[2] = long.MinValue;
+            longs[3] = 12345678901;
+            longs[4] = -12345678901;
+            for (int i = 5; i < numberOfItems; i++)
+            {
+                long value = random.Next(int.MinValue, int.MaxValue);
+                value += value < 0 ? int.MinValue : int.MaxValue;
+                longs[i] = value;
+            }
+
+            var ulongs = new ulong[numberOfItems];
+            ulongs[0] = ulong.MaxValue;
+            ulongs[1] = ulong.MinValue;
+            ulongs[2] = 10446744073709551615;
+            for (int i = 3; i < numberOfItems; i++)
+            {
+
+            }
+
+            var doubles = new double[numberOfItems * 2];
+            doubles[0] = 0.00;
+            doubles[1] = double.MaxValue;
+            doubles[2] = double.MinValue;
+            doubles[3] = 12.345e1;
+            doubles[4] = -123.45e1;
+            for (int i = 5; i < numberOfItems; i++)
+            {
+                var value = random.NextDouble();
+                if (value < 0.5)
+                {
+                    doubles[i] = random.NextDouble() * double.MinValue;
+                }
+                else
+                {
+                    doubles[i] = random.NextDouble() * double.MaxValue;
+                }
+            }
+
+            for (int i = numberOfItems; i < numberOfItems * 2; i++)
+            {
+                var value = random.NextDouble();
+                if (value < 0.5)
+                {
+                    doubles[i] = random.NextDouble() * -1_000_000;
+                }
+                else
+                {
+                    doubles[i] = random.NextDouble() * 1_000_000;
+                }
+            }
+
+            var floats = new float[numberOfItems];
+            floats[0] = 0.00f;
+            floats[1] = float.MaxValue;
+            floats[2] = float.MinValue;
+            floats[3] = 12.345e1f;
+            floats[4] = -123.45e1f;
+            for (int i = 5; i < numberOfItems; i++)
+            {
+                double mantissa = (random.NextDouble() * 2.0) - 1.0;
+                double exponent = Math.Pow(2.0, random.Next(-126, 128));
+                floats[i] = (float)(mantissa * exponent);
+            }
+
+            var decimals = new decimal[numberOfItems * 2];
+            decimals[0] = (decimal)0.00;
+            decimals[1] = decimal.MaxValue;
+            decimals[2] = decimal.MinValue;
+            decimals[3] = (decimal)12.345e1;
+            decimals[4] = (decimal)-123.45e1;
+            for (int i = 5; i < numberOfItems; i++)
+            {
+                var value = random.NextDouble();
+                if (value < 0.5)
+                {
+                    decimals[i] = (decimal)(random.NextDouble() * -78E14);
+                }
+                else
+                {
+                    decimals[i] = (decimal)(random.NextDouble() * 78E14);
+                }
+            }
+
+            for (int i = numberOfItems; i < numberOfItems * 2; i++)
+            {
+                var value = random.NextDouble();
+                if (value < 0.5)
+                {
+                    decimals[i] = (decimal)(random.NextDouble() * -1_000_000);
+                }
+                else
+                {
+                    decimals[i] = (decimal)(random.NextDouble() * 1_000_000);
+                }
+            }
+
+            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 });
+
+            for (int j = 0; j < 6; j++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                ReadOnlySpan<char> keyUtf16 = keyString;
+                ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (j)
+                {
+                    case 0:
+                        for (int i = 0; i < floats.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, floats[i], escape: false);
+                        for (int i = 0; i < ints.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, ints[i], escape: false);
+                        for (int i = 0; i < uints.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, uints[i], escape: false);
+                        for (int i = 0; i < doubles.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, doubles[i], escape: false);
+                        for (int i = 0; i < longs.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, longs[i], escape: false);
+                        for (int i = 0; i < ulongs.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, ulongs[i], escape: false);
+                        for (int i = 0; i < decimals.Length; i++)
+                            jsonUtf8.WriteNumber(keyString, decimals[i], escape: false);
+                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                        break;
+                    case 1:
+                        for (int i = 0; i < floats.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: false);
+                        for (int i = 0; i < ints.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: false);
+                        for (int i = 0; i < uints.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: false);
+                        for (int i = 0; i < doubles.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: false);
+                        for (int i = 0; i < longs.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: false);
+                        for (int i = 0; i < ulongs.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: false);
+                        for (int i = 0; i < decimals.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: false);
+                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                        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);
+                        for (int i = 0; i < ints.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: true);
+                        for (int i = 0; i < uints.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: true);
+                        for (int i = 0; i < doubles.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: true);
+                        for (int i = 0; i < longs.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: true);
+                        for (int i = 0; i < ulongs.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: true);
+                        for (int i = 0; i < decimals.Length; i++)
+                            jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: true);
+                        jsonUtf8.WriteStartArray(keyUtf8, escape: true);
+                        break;
+                }
+
+                jsonUtf8.WriteNumberValue(floats[0]);
+                jsonUtf8.WriteNumberValue(ints[0]);
+                jsonUtf8.WriteNumberValue(uints[0]);
+                jsonUtf8.WriteNumberValue(doubles[0]);
+                jsonUtf8.WriteNumberValue(longs[0]);
+                jsonUtf8.WriteNumberValue(ulongs[0]);
+                jsonUtf8.WriteNumberValue(decimals[0]);
+                jsonUtf8.WriteEndArray();
+
+                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);
+
+                output.Dispose();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "message")]
+        [InlineData(true, false, "message")]
+        [InlineData(false, true, "message")]
+        [InlineData(false, false, "message")]
+        [InlineData(true, true, "mess><age")]
+        [InlineData(true, false, "mess><age")]
+        [InlineData(false, true, "mess><age")]
+        [InlineData(false, false, "mess><age")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        public void WriteGuidsValue(bool formatted, bool skipValidation, string keyString)
+        {
+            const int numberOfItems = 1_000;
+
+            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 });
+
+            ReadOnlySpan<char> keyUtf16 = keyString;
+            ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyString, guids[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                        break;
+                    case 1:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyUtf16, guids[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteStringValue(guids[0]);
+                jsonUtf8.WriteStringValue(guids[1]);
+                jsonUtf8.WriteEndArray();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "message")]
+        [InlineData(true, false, "message")]
+        [InlineData(false, true, "message")]
+        [InlineData(false, false, "message")]
+        [InlineData(true, true, "mess><age")]
+        [InlineData(true, false, "mess><age")]
+        [InlineData(false, true, "mess><age")]
+        [InlineData(false, false, "mess><age")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyString)
+        {
+            var random = new Random(42);
+            const int numberOfItems = 1_000;
+
+            var start = new DateTime(1995, 1, 1);
+            int range = (DateTime.Today - start).Days;
+
+            var dates = new DateTime[numberOfItems];
+            for (int i = 0; i < numberOfItems; i++)
+                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 });
+
+            ReadOnlySpan<char> keyUtf16 = keyString;
+            ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyString, dates[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                        break;
+                    case 1:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteStringValue(dates[0]);
+                jsonUtf8.WriteStringValue(dates[1]);
+                jsonUtf8.WriteEndArray();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [InlineData(true, true, "message")]
+        [InlineData(true, false, "message")]
+        [InlineData(false, true, "message")]
+        [InlineData(false, false, "message")]
+        [InlineData(true, true, "mess><age")]
+        [InlineData(true, false, "mess><age")]
+        [InlineData(false, true, "mess><age")]
+        [InlineData(false, false, "mess><age")]
+        [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")]
+        public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, string keyString)
+        {
+            var random = new Random(42);
+            const int numberOfItems = 1_000;
+
+            var start = new DateTime(1995, 1, 1);
+            int range = (DateTime.Today - start).Days;
+
+            var dates = new DateTimeOffset[numberOfItems];
+            for (int i = 0; i < numberOfItems; i++)
+                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 });
+
+            ReadOnlySpan<char> keyUtf16 = keyString;
+            ReadOnlySpan<byte> keyUtf8 = Encoding.UTF8.GetBytes(keyString);
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyString, dates[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyString, escape: false);
+                        break;
+                    case 1:
+                        for (int j = 0; j < numberOfItems; j++)
+                            jsonUtf8.WriteString(keyUtf16, dates[j], escape: false);
+                        jsonUtf8.WriteStartArray(keyUtf16, escape: false);
+                        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);
+                        break;
+                }
+
+                jsonUtf8.WriteStringValue(dates[0]);
+                jsonUtf8.WriteStringValue(dates[1]);
+                jsonUtf8.WriteEndArray();
+
+                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();
+            }
+        }
+
+        [Theory]
+        [OuterLoop]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteLargeKeyOrValue(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            Span<byte> key = new byte[1_000_000_001];
+            key.Fill((byte)'a');
+            Span<byte> value = new byte[1_000_000_001];
+            value.Fill((byte)'b');
+
+            var output = new ArrayBufferWriter(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            try
+            {
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteString(key, DateTime.Now, escape: false);
+                Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. KeyLength: {key.Length}");
+            }
+            catch (ArgumentException) { }
+
+            try
+            {
+                jsonUtf8.WriteStartArray();
+                jsonUtf8.WriteStringValue(value, escape: false);
+                Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. ValueLength: {value.Length}");
+            }
+            catch (ArgumentException) { }
+
+            output.Dispose();
+        }
+
+        [Theory]
+        [OuterLoop]
+        [InlineData(true, true)]
+        [InlineData(true, false)]
+        [InlineData(false, true)]
+        [InlineData(false, false)]
+        public void WriteLargeKeyValue(bool formatted, bool skipValidation)
+        {
+            var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation });
+
+            Span<byte> key = new byte[1_000_000_001];
+            key.Fill((byte)'a');
+            Span<byte> value = new byte[1_000_000_001];
+            value.Fill((byte)'b');
+
+            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);
+        }
+
+        private static void WriteTooLargeHelper(JsonWriterState state, ReadOnlySpan<byte> key, ReadOnlySpan<byte> value, bool noThrow = false)
+        {
+            var output = new ArrayBufferWriter(1024);
+            var jsonUtf8 = new Utf8JsonWriter(output, state);
+
+            jsonUtf8.WriteStartObject();
+
+            try
+            {
+                jsonUtf8.WriteString(key, value, escape: false);
+
+                if (!noThrow)
+                {
+                    Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. KeyLength: {key.Length} | ValueLength: {value.Length}");
+                }
+            }
+            catch (ArgumentException)
+            {
+                if (noThrow)
+                {
+                    Assert.True(false, $"Expected writing large key/value to succeed. KeyLength: {key.Length} | ValueLength: {value.Length}");
+                }
+            }
+
+            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)
+        {
+            MemoryStream 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.WriteStartObject();
+            json.WritePropertyName("message");
+            json.WriteValue("Hello, World!");
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetCommentExpectedString(bool prettyPrint, string comment)
+        {
+            MemoryStream 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,
+            };
+
+            json.WriteStartArray();
+            for (int j = 0; j < 10; j++)
+                json.WriteComment(comment);
+            json.WriteValue(comment);
+            json.WriteComment(comment);
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStringsExpectedString(bool prettyPrint, string value)
+        {
+            MemoryStream 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 j = 0; j < 10; j++)
+                json.WriteValue(value);
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetEscapedExpectedString(bool prettyPrint, string propertyName, string value, StringEscapeHandling escaping, bool escape = true)
+        {
+            MemoryStream 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 = escaping
+            };
+
+            json.WriteStartObject();
+            json.WritePropertyName(propertyName, escape);
+            json.WriteValue(value);
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetCustomExpectedString(bool prettyPrint)
+        {
+            MemoryStream 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.WriteStartObject();
+            for (int i = 0; i < 1_000; i++)
+            {
+                json.WritePropertyName("message");
+                json.WriteValue("Hello, World!");
+            }
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStartEndExpectedString(bool prettyPrint)
+        {
+            MemoryStream 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();
+            json.WriteStartObject();
+            json.WriteEnd();
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStartEndWithPropertyArrayExpectedString(bool prettyPrint)
+        {
+            MemoryStream 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.WriteStartObject();
+            json.WritePropertyName("property name");
+            json.WriteStartArray();
+            json.WriteEnd();
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStartEndWithPropertyArrayExpectedString(string key, bool prettyPrint, bool escape = false)
+        {
+            MemoryStream 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
+            };
+
+            json.WriteStartObject();
+            json.WritePropertyName(key, escape);
+            json.WriteStartArray();
+            json.WriteEnd();
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStartEndWithPropertyObjectExpectedString(bool prettyPrint)
+        {
+            MemoryStream 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.WriteStartObject();
+            json.WritePropertyName("property name");
+            json.WriteStartObject();
+            json.WriteEnd();
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetStartEndWithPropertyObjectExpectedString(string key, bool prettyPrint, bool escape = false)
+        {
+            MemoryStream 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
+            };
+
+            json.WriteStartObject();
+            json.WritePropertyName(key, escape);
+            json.WriteStartObject();
+            json.WriteEnd();
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetArrayWithPropertyExpectedString(bool prettyPrint)
+        {
+            MemoryStream 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.WriteStartObject();
+            json.WritePropertyName("message");
+            json.WriteStartArray();
+            json.WriteEndArray();
+            json.WriteEndObject();
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetBooleanExpectedString(bool prettyPrint, string keyString, bool value, bool escape = false)
+        {
+            MemoryStream 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,
+            };
+
+            json.WriteStartObject();
+            json.WritePropertyName(keyString, escape);
+            json.WriteValue(value);
+
+            json.WritePropertyName("temp");
+            json.WriteStartArray();
+            json.WriteValue(true);
+            json.WriteValue(true);
+            json.WriteValue(false);
+            json.WriteValue(false);
+            json.WriteEnd();
+
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetNullExpectedString(bool prettyPrint, string keyString, bool escape = false)
+        {
+            MemoryStream 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,
+            };
+
+            json.WriteStartObject();
+            json.WritePropertyName(keyString, escape);
+            json.WriteNull();
+
+            json.WritePropertyName("temp");
+            json.WriteStartArray();
+            json.WriteValue((string)null);
+            json.WriteValue((string)null);
+            json.WriteEnd();
+
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetIntegerExpectedString(bool prettyPrint, int value)
+        {
+            MemoryStream 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.WriteStartObject();
+            json.WritePropertyName("message");
+            json.WriteValue(value);
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        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();
+            TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+            var json = new JsonTextWriter(streamWriter)
+            {
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None
+            };
+
+            json.WriteStartObject();
+
+            for (int i = 0; i < floats.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(floats[i]);
+            }
+            for (int i = 0; i < ints.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(ints[i]);
+            }
+            for (int i = 0; i < uints.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(uints[i]);
+            }
+            for (int i = 0; i < doubles.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(doubles[i]);
+            }
+            for (int i = 0; i < longs.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(longs[i]);
+            }
+            for (int i = 0; i < ulongs.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(ulongs[i]);
+            }
+            for (int i = 0; i < decimals.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(decimals[i]);
+            }
+
+            json.WritePropertyName(keyString, escape);
+            json.WriteStartArray();
+            json.WriteValue(floats[0]);
+            json.WriteValue(ints[0]);
+            json.WriteValue(uints[0]);
+            json.WriteValue(doubles[0]);
+            json.WriteValue(longs[0]);
+            json.WriteValue(ulongs[0]);
+            json.WriteValue(decimals[0]);
+            json.WriteEndArray();
+
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetGuidsExpectedString(bool prettyPrint, string keyString, Guid[] guids, bool escape = false)
+        {
+            MemoryStream 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
+            };
+
+            json.WriteStartObject();
+
+            for (int i = 0; i < guids.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(guids[i]);
+            }
+
+            json.WritePropertyName(keyString, escape);
+            json.WriteStartArray();
+            json.WriteValue(guids[0]);
+            json.WriteValue(guids[1]);
+            json.WriteEnd();
+
+            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();
+            TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+            var json = new JsonTextWriter(streamWriter)
+            {
+                Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
+                StringEscapeHandling = StringEscapeHandling.EscapeHtml,
+                DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffffff"
+            };
+
+            json.WriteStartObject();
+
+            for (int i = 0; i < dates.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(dates[i]);
+            }
+
+            json.WritePropertyName(keyString, escape);
+            json.WriteStartArray();
+            json.WriteValue(dates[0]);
+            json.WriteValue(dates[1]);
+            json.WriteEnd();
+
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+
+        private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTimeOffset[] dates, bool escape = false)
+        {
+            MemoryStream 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,
+                DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffffffzzz"
+            };
+
+            json.WriteStartObject();
+
+            for (int i = 0; i < dates.Length; i++)
+            {
+                json.WritePropertyName(keyString, escape);
+                json.WriteValue(dates[i]);
+            }
+
+            json.WritePropertyName(keyString, escape);
+            json.WriteStartArray();
+            json.WriteValue(dates[0]);
+            json.WriteValue(dates[1]);
+            json.WriteEnd();
+
+            json.WriteEnd();
+
+            json.Flush();
+
+            return Encoding.UTF8.GetString(ms.ToArray());
+        }
+    }
+}