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

61 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/ConsumeNumberResult.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/ConsumeTokenResult.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs
src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderException.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderHelper.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderOptions.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderState.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeNumberResult.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeTokenResult.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderException.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.MultiSegment.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.TryGet.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.cs [deleted file]
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 b95392a5e26a8d5eb0870ff56b6ab3c6576c481c..25aa94e83bc6837c5984b04adbbfb0fc887875d2 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 c1c47799045be2bd8d00e652743e97dfea790832..33d1d0f6a57889caddde9098c301a054c8e74f76 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 a158dabc85f311ba90b344ce54646aeb58728609..6d774c2bcc32cb5c5ec8c228e0199cb318e22301 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>
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ConsumeNumberResult.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ConsumeNumberResult.cs
deleted file mode 100644 (file)
index cf0b05c..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// This enum captures the tri-state return value when trying to read a
-    /// JSON number.
-    /// </summary>
-    internal enum ConsumeNumberResult : byte
-    {
-        /// <summary>
-        /// Reached a valid end of number and hence no action is required.
-        /// </summary>
-        Success,
-        /// <summary>
-        /// Successfully processed a portion of the number and need to
-        /// read to the next region of the number.
-        /// </summary>
-        OperationIncomplete,
-        /// <summary>
-        /// Observed incomplete data.
-        /// Return false if we have more data to follow. Otherwise throw.
-        /// </summary>
-        NeedMoreData,
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ConsumeTokenResult.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ConsumeTokenResult.cs
deleted file mode 100644 (file)
index 89414ba..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// This enum captures the tri-state return value when trying to read the
-    /// next JSON token.
-    /// </summary>
-    internal enum ConsumeTokenResult : byte
-    {
-        /// <summary>
-        /// Reached a valid end of token and hence no action is required.
-        /// </summary>
-        Success,
-        /// <summary>
-        /// Observed incomplete data but progressed state partially in looking ahead.
-        /// Return false and roll-back to a previously saved state.
-        /// </summary>
-        NotEnoughDataRollBackState,
-        /// <summary>
-        /// Observed incomplete data but no change was made to the state.
-        /// Return false, but do not roll-back anything since nothing changed.
-        /// </summary>
-        IncompleteNoRollBackNecessary,
-    }
-}
index 859d70611f907d4dfe5478a786bd7ddd615d2ca2..c56f8d91d15c28ac3252ccf2dd8d2a0d66c2bac2 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;
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderException.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderException.cs
deleted file mode 100644 (file)
index 9b6910b..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Runtime.Serialization;
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// Defines a custom exception object that is thrown by the <see cref="Utf8JsonReader"/> whenever it
-    /// encounters an invalid JSON text while reading through it. This exception is also thrown
-    /// whenever you read past the defined maximum depth.
-    /// </summary>
-    [Serializable]
-    public sealed class JsonReaderException : Exception
-    {
-        /// <summary>
-        /// Creates a new exception object to relay error information to the user.
-        /// </summary>
-        /// <param name="message">The context specific error message.</param>
-        /// <param name="lineNumber">The line number at which the invalid JSON was encountered (starting at 0).</param>
-        /// <param name="bytePositionInLine">The byte count within the current line where the invalid JSON was encountered (starting at 0).</param>
-        /// <remarks>
-        /// Note that the <paramref name="bytePositionInLine"/> counts the number of bytes (i.e. UTF-8 code units) and not characters or scalars.
-        /// </remarks>
-        public JsonReaderException(string message, long lineNumber, long bytePositionInLine) : base(message)
-        {
-            LineNumber = lineNumber;
-            BytePositionInLine = bytePositionInLine;
-        }
-
-        private JsonReaderException(SerializationInfo info, StreamingContext context) : base(info, context)
-        {
-            LineNumber = info.GetInt64("LineNumber");
-            BytePositionInLine = info.GetInt64("BytePositionInLine");
-        }
-
-        /// <summary>
-        ///  Sets the <see cref="SerializationInfo"/> with information about the exception.
-        /// </summary>
-        /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
-        /// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
-        public override void GetObjectData(SerializationInfo info, StreamingContext context)
-        {
-            base.GetObjectData(info, context);
-            info.AddValue("LineNumber", LineNumber, typeof(long));
-            info.AddValue("BytePositionInLine", BytePositionInLine, typeof(long));
-        }
-
-        /// <summary>
-        /// The number of lines read so far before the exception (starting at 0).
-        /// </summary>
-        public long LineNumber { get; private set; }
-
-        /// <summary>
-        /// The number of bytes read within the current line before the exception (starting at 0).
-        /// </summary>
-        public long BytePositionInLine { get; private set; }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderHelper.cs
deleted file mode 100644 (file)
index d8a0ea6..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-using Internal.Runtime.CompilerServices;
-
-namespace System.Text.Json
-{
-    internal static class JsonReaderHelper
-    {
-        public static (int, int) CountNewLines(ReadOnlySpan<byte> data)
-        {
-            int lastLineFeedIndex = -1;
-            int newLines = 0;
-            for (int i = 0; i < data.Length; i++)
-            {
-                if (data[i] == JsonConstants.LineFeed)
-                {
-                    lastLineFeedIndex = i;
-                    newLines++;
-                }
-            }
-            return (newLines, lastLineFeedIndex);
-        }
-
-        // A digit is valid if it is in the range: [0..9]
-        // Otherwise, return false.
-        public static bool IsDigit(byte nextByte) => (uint)(nextByte - '0') <= '9' - '0';
-
-        // Returns true if the TokenType is a primitive "value", i.e. String, Number, True, False, and Null
-        // Otherwise, return false.
-        public static bool IsTokenTypePrimitive(JsonTokenType tokenType) =>
-            (tokenType - JsonTokenType.String) <= (JsonTokenType.Null - JsonTokenType.String);
-
-        // A hex digit is valid if it is in the range: [0..9] | [A..F] | [a..f]
-        // Otherwise, return false.
-        public static bool IsHexDigit(byte nextByte) =>
-            (uint)(nextByte - '0') <= '9' - '0' ||
-            (uint)(nextByte - 'A') <= 'F' - 'A' ||
-            (uint)(nextByte - 'a') <= 'f' - 'a';
-
-        // https://tools.ietf.org/html/rfc8259
-        // Does the span contain '"', '\',  or any control characters (i.e. 0 to 31)
-        // IndexOfAny(34, 92, < 32)
-        // Borrowed and modified from SpanHelpers.Byte:
-        // https://github.com/dotnet/corefx/blob/fc169cddedb6820aaabbdb8b7bece2a3df0fd1a5/src/Common/src/CoreLib/System/SpanHelpers.Byte.cs#L473-L604
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan<byte> span)
-        {
-            return IndexOfOrLessThan(
-                    ref MemoryMarshal.GetReference(span),
-                    JsonConstants.Quote,
-                    JsonConstants.BackSlash,
-                    lessThan: 32,   // Space ' '
-                    span.Length);
-        }
-
-        private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, byte value1, byte lessThan, int length)
-        {
-            Debug.Assert(length >= 0);
-
-            uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
-            uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
-            uint uLessThan = lessThan; // Use uint for comparisons to avoid unnecessary 8->32 extensions
-            IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
-            IntPtr nLength = (IntPtr)length;
-
-            if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
-            {
-                int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
-                nLength = (IntPtr)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
-            }
-        SequentialScan:
-            uint lookUp;
-            while ((byte*)nLength >= (byte*)8)
-            {
-                nLength -= 8;
-
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found1;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found2;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found3;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found4;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found5;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found6;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found7;
-
-                index += 8;
-            }
-
-            if ((byte*)nLength >= (byte*)4)
-            {
-                nLength -= 4;
-
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found1;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found2;
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found3;
-
-                index += 4;
-            }
-
-            while ((byte*)nLength > (byte*)0)
-            {
-                nLength -= 1;
-
-                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
-                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
-                    goto Found;
-
-                index += 1;
-            }
-
-            if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
-            {
-                nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<byte>.Count - 1));
-
-                // Get comparison Vector
-                Vector<byte> values0 = new Vector<byte>(value0);
-                Vector<byte> values1 = new Vector<byte>(value1);
-                Vector<byte> valuesLessThan = new Vector<byte>(lessThan);
-
-                while ((byte*)nLength > (byte*)index)
-                {
-                    Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
-
-                    var vMatches = Vector.BitwiseOr(
-                                    Vector.BitwiseOr(
-                                        Vector.Equals(vData, values0),
-                                        Vector.Equals(vData, values1)),
-                                    Vector.LessThan(vData, valuesLessThan));
-
-                    if (Vector<byte>.Zero.Equals(vMatches))
-                    {
-                        index += Vector<byte>.Count;
-                        continue;
-                    }
-                    // Find offset of first match
-                    return (int)(byte*)index + LocateFirstFoundByte(vMatches);
-                }
-
-                if ((int)(byte*)index < length)
-                {
-                    nLength = (IntPtr)(length - (int)(byte*)index);
-                    goto SequentialScan;
-                }
-            }
-            return -1;
-        Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
-            return (int)(byte*)index;
-        Found1:
-            return (int)(byte*)(index + 1);
-        Found2:
-            return (int)(byte*)(index + 2);
-        Found3:
-            return (int)(byte*)(index + 3);
-        Found4:
-            return (int)(byte*)(index + 4);
-        Found5:
-            return (int)(byte*)(index + 5);
-        Found6:
-            return (int)(byte*)(index + 6);
-        Found7:
-            return (int)(byte*)(index + 7);
-        }
-
-        // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static int LocateFirstFoundByte(Vector<byte> match)
-        {
-            var vector64 = Vector.AsVectorUInt64(match);
-            ulong candidate = 0;
-            int i = 0;
-            // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
-            for (; i < Vector<ulong>.Count; i++)
-            {
-                candidate = vector64[i];
-                if (candidate != 0)
-                {
-                    break;
-                }
-            }
-
-            // Single LEA instruction with jitted const (using function result)
-            return i * 8 + LocateFirstFoundByte(candidate);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static int LocateFirstFoundByte(ulong match)
-        {
-            // Flag least significant power of two bit
-            var powerOfTwoFlag = match ^ (match - 1);
-            // Shift all powers of two into the high byte and extract
-            return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57);
-        }
-
-        private const ulong XorPowerOfTwoToHighByte = (0x07ul |
-                                               0x06ul << 8 |
-                                               0x05ul << 16 |
-                                               0x04ul << 24 |
-                                               0x03ul << 32 |
-                                               0x02ul << 40 |
-                                               0x01ul << 48) + 1;
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderOptions.cs
deleted file mode 100644 (file)
index 439120a..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// Provides the ability for the user to define custom behavior when reading JSON
-    /// using the <see cref="Utf8JsonReader"/> that may deviate from strict adherence
-    /// to the JSON specification (as per the JSON RFC - https://tools.ietf.org/html/rfc8259),
-    /// which is the default behavior.
-    /// </summary>
-    public struct JsonReaderOptions
-    {
-        /// <summary>
-        /// Defines how the <see cref="Utf8JsonReader"/> should handle comments when reading through the JSON.
-        /// </summary>
-        public JsonCommentHandling CommentHandling { get; set; }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonReaderState.cs
deleted file mode 100644 (file)
index 9f6b41c..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// Defines an opaque type that holds and saves all the relevant state information which must be provided
-    /// to the <see cref="Utf8JsonReader"/> to continue reading after processing incomplete data.
-    /// This type is required to support reentrancy when reading incomplete data, and to continue
-    /// reading once more data is available. Unlike the <see cref="Utf8JsonReader"/>, which is a ref struct,
-    /// this type 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="Utf8JsonReader"/>.
-    /// </summary>
-    public struct JsonReaderState
-    {
-        internal const int DefaultMaxDepth = 64;
-
-        internal long _lineNumber;
-        internal long _bytePositionInLine;
-        internal long _bytesConsumed;
-        internal int _maxDepth;
-        internal bool _inObject;
-        internal bool _isNotPrimitive;
-        internal char _numberFormat;
-        internal JsonTokenType _tokenType;
-        internal JsonTokenType _previousTokenType;
-        internal JsonReaderOptions _readerOptions;
-        internal BitStack _bitStack;
-        internal SequencePosition _sequencePosition;
-
-        /// <summary>
-        /// Returns the total amount of bytes consumed by the <see cref="Utf8JsonReader"/> so far
-        /// for the given UTF-8 encoded input text.
-        /// </summary>
-        public long BytesConsumed => _bytesConsumed;
-
-        /// <summary>
-        /// Returns the current <see cref="SequencePosition"/> within the provided UTF-8 encoded
-        /// input ReadOnlySequence&lt;byte&gt;. If the <see cref="Utf8JsonReader"/> was constructed
-        /// with a ReadOnlySpan&lt;byte&gt; instead, this will always return a default <see cref="SequencePosition"/>.
-        /// </summary>
-        public SequencePosition Position => _sequencePosition;
-
-        /// <summary>
-        /// Constructs a new <see cref="JsonReaderState"/> instance.
-        /// </summary>
-        /// <param name="maxDepth">Sets the maximum depth allowed when reading JSON, with the default set as 64.
-        /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.</param>
-        /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonReader"/>
-        /// that is different from the JSON RFC (for example how to handle comments).
-        /// By default, the <see cref="Utf8JsonReader"/> follows the JSON RFC strictly (i.e. comments within the JSON are invalid).</param>
-        /// <exception cref="ArgumentException">
-        /// Thrown when the max depth is set to a non-positive value (&lt;= 0)
-        /// </exception>
-        /// <remarks>
-        /// An instance of this state must be passed to the <see cref="Utf8JsonReader"/> ctor with the JSON data.
-        /// Unlike the <see cref="Utf8JsonReader"/>, 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="Utf8JsonReader"/>.
-        /// </remarks>
-        public JsonReaderState(int maxDepth = DefaultMaxDepth, JsonReaderOptions options = default)
-        {
-            if (maxDepth <= 0)
-                throw ThrowHelper.GetArgumentException_MaxDepthMustBePositive();
-
-            _lineNumber = default;
-            _bytePositionInLine = default;
-            _bytesConsumed = default;
-            _maxDepth = maxDepth;
-            _inObject = default;
-            _isNotPrimitive = default;
-            _numberFormat = default;
-            _tokenType = default;
-            _previousTokenType = default;
-            _readerOptions = options;
-
-            // Only allocate if the user reads 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;
-
-            _sequencePosition = default;
-        }
-
-        /// <summary>
-        /// Gets the custom behavior when reading JSON using
-        /// the <see cref="Utf8JsonReader"/> that may deviate from strict adherence
-        /// to the JSON specification, which is the default behavior.
-        /// </summary>
-        public JsonReaderOptions Options => _readerOptions;
-
-        /// <summary>
-        /// Gets or sets the maximum depth allowed when reading JSON.
-        /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.
-        /// </summary>
-        public int MaxDepth => _maxDepth;
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeNumberResult.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeNumberResult.cs
new file mode 100644 (file)
index 0000000..cf0b05c
--- /dev/null
@@ -0,0 +1,28 @@
+// 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>
+    /// This enum captures the tri-state return value when trying to read a
+    /// JSON number.
+    /// </summary>
+    internal enum ConsumeNumberResult : byte
+    {
+        /// <summary>
+        /// Reached a valid end of number and hence no action is required.
+        /// </summary>
+        Success,
+        /// <summary>
+        /// Successfully processed a portion of the number and need to
+        /// read to the next region of the number.
+        /// </summary>
+        OperationIncomplete,
+        /// <summary>
+        /// Observed incomplete data.
+        /// Return false if we have more data to follow. Otherwise throw.
+        /// </summary>
+        NeedMoreData,
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeTokenResult.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/ConsumeTokenResult.cs
new file mode 100644 (file)
index 0000000..89414ba
--- /dev/null
@@ -0,0 +1,28 @@
+// 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>
+    /// This enum captures the tri-state return value when trying to read the
+    /// next JSON token.
+    /// </summary>
+    internal enum ConsumeTokenResult : byte
+    {
+        /// <summary>
+        /// Reached a valid end of token and hence no action is required.
+        /// </summary>
+        Success,
+        /// <summary>
+        /// Observed incomplete data but progressed state partially in looking ahead.
+        /// Return false and roll-back to a previously saved state.
+        /// </summary>
+        NotEnoughDataRollBackState,
+        /// <summary>
+        /// Observed incomplete data but no change was made to the state.
+        /// Return false, but do not roll-back anything since nothing changed.
+        /// </summary>
+        IncompleteNoRollBackNecessary,
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderException.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderException.cs
new file mode 100644 (file)
index 0000000..9b6910b
--- /dev/null
@@ -0,0 +1,60 @@
+// 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.Runtime.Serialization;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    /// Defines a custom exception object that is thrown by the <see cref="Utf8JsonReader"/> whenever it
+    /// encounters an invalid JSON text while reading through it. This exception is also thrown
+    /// whenever you read past the defined maximum depth.
+    /// </summary>
+    [Serializable]
+    public sealed class JsonReaderException : Exception
+    {
+        /// <summary>
+        /// Creates a new exception object to relay error information to the user.
+        /// </summary>
+        /// <param name="message">The context specific error message.</param>
+        /// <param name="lineNumber">The line number at which the invalid JSON was encountered (starting at 0).</param>
+        /// <param name="bytePositionInLine">The byte count within the current line where the invalid JSON was encountered (starting at 0).</param>
+        /// <remarks>
+        /// Note that the <paramref name="bytePositionInLine"/> counts the number of bytes (i.e. UTF-8 code units) and not characters or scalars.
+        /// </remarks>
+        public JsonReaderException(string message, long lineNumber, long bytePositionInLine) : base(message)
+        {
+            LineNumber = lineNumber;
+            BytePositionInLine = bytePositionInLine;
+        }
+
+        private JsonReaderException(SerializationInfo info, StreamingContext context) : base(info, context)
+        {
+            LineNumber = info.GetInt64("LineNumber");
+            BytePositionInLine = info.GetInt64("BytePositionInLine");
+        }
+
+        /// <summary>
+        ///  Sets the <see cref="SerializationInfo"/> with information about the exception.
+        /// </summary>
+        /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+        /// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
+        public override void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            base.GetObjectData(info, context);
+            info.AddValue("LineNumber", LineNumber, typeof(long));
+            info.AddValue("BytePositionInLine", BytePositionInLine, typeof(long));
+        }
+
+        /// <summary>
+        /// The number of lines read so far before the exception (starting at 0).
+        /// </summary>
+        public long LineNumber { get; private set; }
+
+        /// <summary>
+        /// The number of bytes read within the current line before the exception (starting at 0).
+        /// </summary>
+        public long BytePositionInLine { get; private set; }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
new file mode 100644 (file)
index 0000000..d8a0ea6
--- /dev/null
@@ -0,0 +1,234 @@
+// 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.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System.Text.Json
+{
+    internal static class JsonReaderHelper
+    {
+        public static (int, int) CountNewLines(ReadOnlySpan<byte> data)
+        {
+            int lastLineFeedIndex = -1;
+            int newLines = 0;
+            for (int i = 0; i < data.Length; i++)
+            {
+                if (data[i] == JsonConstants.LineFeed)
+                {
+                    lastLineFeedIndex = i;
+                    newLines++;
+                }
+            }
+            return (newLines, lastLineFeedIndex);
+        }
+
+        // A digit is valid if it is in the range: [0..9]
+        // Otherwise, return false.
+        public static bool IsDigit(byte nextByte) => (uint)(nextByte - '0') <= '9' - '0';
+
+        // Returns true if the TokenType is a primitive "value", i.e. String, Number, True, False, and Null
+        // Otherwise, return false.
+        public static bool IsTokenTypePrimitive(JsonTokenType tokenType) =>
+            (tokenType - JsonTokenType.String) <= (JsonTokenType.Null - JsonTokenType.String);
+
+        // A hex digit is valid if it is in the range: [0..9] | [A..F] | [a..f]
+        // Otherwise, return false.
+        public static bool IsHexDigit(byte nextByte) =>
+            (uint)(nextByte - '0') <= '9' - '0' ||
+            (uint)(nextByte - 'A') <= 'F' - 'A' ||
+            (uint)(nextByte - 'a') <= 'f' - 'a';
+
+        // https://tools.ietf.org/html/rfc8259
+        // Does the span contain '"', '\',  or any control characters (i.e. 0 to 31)
+        // IndexOfAny(34, 92, < 32)
+        // Borrowed and modified from SpanHelpers.Byte:
+        // https://github.com/dotnet/corefx/blob/fc169cddedb6820aaabbdb8b7bece2a3df0fd1a5/src/Common/src/CoreLib/System/SpanHelpers.Byte.cs#L473-L604
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan<byte> span)
+        {
+            return IndexOfOrLessThan(
+                    ref MemoryMarshal.GetReference(span),
+                    JsonConstants.Quote,
+                    JsonConstants.BackSlash,
+                    lessThan: 32,   // Space ' '
+                    span.Length);
+        }
+
+        private static unsafe int IndexOfOrLessThan(ref byte searchSpace, byte value0, byte value1, byte lessThan, int length)
+        {
+            Debug.Assert(length >= 0);
+
+            uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+            uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+            uint uLessThan = lessThan; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+            IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+            IntPtr nLength = (IntPtr)length;
+
+            if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+            {
+                int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+                nLength = (IntPtr)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+            }
+        SequentialScan:
+            uint lookUp;
+            while ((byte*)nLength >= (byte*)8)
+            {
+                nLength -= 8;
+
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found1;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found2;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found3;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found4;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found5;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found6;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found7;
+
+                index += 8;
+            }
+
+            if ((byte*)nLength >= (byte*)4)
+            {
+                nLength -= 4;
+
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found1;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found2;
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found3;
+
+                index += 4;
+            }
+
+            while ((byte*)nLength > (byte*)0)
+            {
+                nLength -= 1;
+
+                lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+                if (uValue0 == lookUp || uValue1 == lookUp || uLessThan > lookUp)
+                    goto Found;
+
+                index += 1;
+            }
+
+            if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+            {
+                nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<byte>.Count - 1));
+
+                // Get comparison Vector
+                Vector<byte> values0 = new Vector<byte>(value0);
+                Vector<byte> values1 = new Vector<byte>(value1);
+                Vector<byte> valuesLessThan = new Vector<byte>(lessThan);
+
+                while ((byte*)nLength > (byte*)index)
+                {
+                    Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
+
+                    var vMatches = Vector.BitwiseOr(
+                                    Vector.BitwiseOr(
+                                        Vector.Equals(vData, values0),
+                                        Vector.Equals(vData, values1)),
+                                    Vector.LessThan(vData, valuesLessThan));
+
+                    if (Vector<byte>.Zero.Equals(vMatches))
+                    {
+                        index += Vector<byte>.Count;
+                        continue;
+                    }
+                    // Find offset of first match
+                    return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+                }
+
+                if ((int)(byte*)index < length)
+                {
+                    nLength = (IntPtr)(length - (int)(byte*)index);
+                    goto SequentialScan;
+                }
+            }
+            return -1;
+        Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+            return (int)(byte*)index;
+        Found1:
+            return (int)(byte*)(index + 1);
+        Found2:
+            return (int)(byte*)(index + 2);
+        Found3:
+            return (int)(byte*)(index + 3);
+        Found4:
+            return (int)(byte*)(index + 4);
+        Found5:
+            return (int)(byte*)(index + 5);
+        Found6:
+            return (int)(byte*)(index + 6);
+        Found7:
+            return (int)(byte*)(index + 7);
+        }
+
+        // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static int LocateFirstFoundByte(Vector<byte> match)
+        {
+            var vector64 = Vector.AsVectorUInt64(match);
+            ulong candidate = 0;
+            int i = 0;
+            // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+            for (; i < Vector<ulong>.Count; i++)
+            {
+                candidate = vector64[i];
+                if (candidate != 0)
+                {
+                    break;
+                }
+            }
+
+            // Single LEA instruction with jitted const (using function result)
+            return i * 8 + LocateFirstFoundByte(candidate);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static int LocateFirstFoundByte(ulong match)
+        {
+            // Flag least significant power of two bit
+            var powerOfTwoFlag = match ^ (match - 1);
+            // Shift all powers of two into the high byte and extract
+            return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57);
+        }
+
+        private const ulong XorPowerOfTwoToHighByte = (0x07ul |
+                                               0x06ul << 8 |
+                                               0x05ul << 16 |
+                                               0x04ul << 24 |
+                                               0x03ul << 32 |
+                                               0x02ul << 40 |
+                                               0x01ul << 48) + 1;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs
new file mode 100644 (file)
index 0000000..439120a
--- /dev/null
@@ -0,0 +1,20 @@
+// 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 reading JSON
+    /// using the <see cref="Utf8JsonReader"/> that may deviate from strict adherence
+    /// to the JSON specification (as per the JSON RFC - https://tools.ietf.org/html/rfc8259),
+    /// which is the default behavior.
+    /// </summary>
+    public struct JsonReaderOptions
+    {
+        /// <summary>
+        /// Defines how the <see cref="Utf8JsonReader"/> should handle comments when reading through the JSON.
+        /// </summary>
+        public JsonCommentHandling CommentHandling { get; set; }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs
new file mode 100644 (file)
index 0000000..9f6b41c
--- /dev/null
@@ -0,0 +1,98 @@
+// 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="Utf8JsonReader"/> to continue reading after processing incomplete data.
+    /// This type is required to support reentrancy when reading incomplete data, and to continue
+    /// reading once more data is available. Unlike the <see cref="Utf8JsonReader"/>, which is a ref struct,
+    /// this type 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="Utf8JsonReader"/>.
+    /// </summary>
+    public struct JsonReaderState
+    {
+        internal const int DefaultMaxDepth = 64;
+
+        internal long _lineNumber;
+        internal long _bytePositionInLine;
+        internal long _bytesConsumed;
+        internal int _maxDepth;
+        internal bool _inObject;
+        internal bool _isNotPrimitive;
+        internal char _numberFormat;
+        internal JsonTokenType _tokenType;
+        internal JsonTokenType _previousTokenType;
+        internal JsonReaderOptions _readerOptions;
+        internal BitStack _bitStack;
+        internal SequencePosition _sequencePosition;
+
+        /// <summary>
+        /// Returns the total amount of bytes consumed by the <see cref="Utf8JsonReader"/> so far
+        /// for the given UTF-8 encoded input text.
+        /// </summary>
+        public long BytesConsumed => _bytesConsumed;
+
+        /// <summary>
+        /// Returns the current <see cref="SequencePosition"/> within the provided UTF-8 encoded
+        /// input ReadOnlySequence&lt;byte&gt;. If the <see cref="Utf8JsonReader"/> was constructed
+        /// with a ReadOnlySpan&lt;byte&gt; instead, this will always return a default <see cref="SequencePosition"/>.
+        /// </summary>
+        public SequencePosition Position => _sequencePosition;
+
+        /// <summary>
+        /// Constructs a new <see cref="JsonReaderState"/> instance.
+        /// </summary>
+        /// <param name="maxDepth">Sets the maximum depth allowed when reading JSON, with the default set as 64.
+        /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.</param>
+        /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonReader"/>
+        /// that is different from the JSON RFC (for example how to handle comments).
+        /// By default, the <see cref="Utf8JsonReader"/> follows the JSON RFC strictly (i.e. comments within the JSON are invalid).</param>
+        /// <exception cref="ArgumentException">
+        /// Thrown when the max depth is set to a non-positive value (&lt;= 0)
+        /// </exception>
+        /// <remarks>
+        /// An instance of this state must be passed to the <see cref="Utf8JsonReader"/> ctor with the JSON data.
+        /// Unlike the <see cref="Utf8JsonReader"/>, 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="Utf8JsonReader"/>.
+        /// </remarks>
+        public JsonReaderState(int maxDepth = DefaultMaxDepth, JsonReaderOptions options = default)
+        {
+            if (maxDepth <= 0)
+                throw ThrowHelper.GetArgumentException_MaxDepthMustBePositive();
+
+            _lineNumber = default;
+            _bytePositionInLine = default;
+            _bytesConsumed = default;
+            _maxDepth = maxDepth;
+            _inObject = default;
+            _isNotPrimitive = default;
+            _numberFormat = default;
+            _tokenType = default;
+            _previousTokenType = default;
+            _readerOptions = options;
+
+            // Only allocate if the user reads 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;
+
+            _sequencePosition = default;
+        }
+
+        /// <summary>
+        /// Gets the custom behavior when reading JSON using
+        /// the <see cref="Utf8JsonReader"/> that may deviate from strict adherence
+        /// to the JSON specification, which is the default behavior.
+        /// </summary>
+        public JsonReaderOptions Options => _readerOptions;
+
+        /// <summary>
+        /// Gets or sets the maximum depth allowed when reading JSON.
+        /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.
+        /// </summary>
+        public int MaxDepth => _maxDepth;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs
new file mode 100644 (file)
index 0000000..51f8c78
--- /dev/null
@@ -0,0 +1,2432 @@
+// 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
+{
+    public ref partial struct Utf8JsonReader
+    {
+        /// <summary>
+        /// Constructs a new <see cref="Utf8JsonReader"/> instance.
+        /// </summary>
+        /// <param name="jsonData">The ReadOnlySequence&lt;byte&gt; containing the UTF-8 encoded JSON text to process.</param>
+        /// <param name="isFinalBlock">True when the input span contains the entire data to process.
+        /// Set to false only if it is known that the input span contains partial data with more data to follow.</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="Utf8JsonReader"/> and pass that back.</param>
+        /// <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="JsonReaderState"/>.
+        /// </remarks>
+        public Utf8JsonReader(in ReadOnlySequence<byte> jsonData, bool isFinalBlock, JsonReaderState state)
+        {
+            _buffer = jsonData.First.Span;
+
+            _isFinalBlock = isFinalBlock;
+
+            // Note: We do not retain _bytesConsumed or _sequencePosition as they reset with the new input data
+            _lineNumber = state._lineNumber;
+            _bytePositionInLine = state._bytePositionInLine;
+            _maxDepth = state._maxDepth == 0 ? JsonReaderState.DefaultMaxDepth : state._maxDepth; // If max depth is not set, revert to the default depth.
+            _inObject = state._inObject;
+            _isNotPrimitive = state._isNotPrimitive;
+            _numberFormat = state._numberFormat;
+            _tokenType = state._tokenType;
+            _previousTokenType = state._previousTokenType;
+            _readerOptions = state._readerOptions;
+            _bitStack = state._bitStack;
+
+            _consumed = 0;
+            _totalConsumed = 0;
+
+            ValueSpan = ReadOnlySpan<byte>.Empty;
+
+            _sequence = jsonData;
+            HasValueSequence = false;
+            ValueSequence = ReadOnlySequence<byte>.Empty;
+
+            if (jsonData.IsSingleSegment)
+            {
+                _nextPosition = default;
+                _currentPosition = default;
+                _isLastSegment = isFinalBlock;
+                _isSingleSegment = true;
+            }
+            else
+            {
+                _nextPosition = jsonData.Start;
+                if (_buffer.Length == 0)
+                {
+                    while (jsonData.TryGet(ref _nextPosition, out ReadOnlyMemory<byte> memory, advance: true))
+                    {
+                        if (memory.Length != 0)
+                        {
+                            _buffer = memory.Span;
+                            break;
+                        }
+                    }
+                }
+
+                _currentPosition = _nextPosition;
+                _isLastSegment = !jsonData.TryGet(ref _nextPosition, out _, advance: true) && isFinalBlock; // Don't re-order to avoid short-circuiting
+                _isSingleSegment = false;
+            }
+        }
+
+        private bool ReadMultiSegment()
+        {
+            bool retVal = false;
+            HasValueSequence = false;
+
+            if (!HasMoreDataMultiSegment())
+            {
+                goto Done;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            // SkipWhiteSpace only skips the whitespace characters as defined by JSON RFC 8259 section 2.
+            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
+            // Those cases are captured in ConsumeNextToken and ConsumeValue.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpaceMultiSegment();
+                if (!HasMoreDataMultiSegment())
+                {
+                    goto Done;
+                }
+                first = _buffer[_consumed];
+            }
+
+            if (_tokenType == JsonTokenType.None)
+            {
+                goto ReadFirstToken;
+            }
+
+            if (first == JsonConstants.Slash)
+            {
+                retVal = ConsumeNextTokenOrRollbackMultiSegment(first);
+                goto Done;
+            }
+
+            if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (first == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+
+                    long prevTotalConsumed = _totalConsumed;
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    retVal = ConsumePropertyNameMultiSegment();
+                    if (!retVal)
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                        _totalConsumed = prevTotalConsumed;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (first == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    retVal = ConsumeValueMultiSegment(first);
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                retVal = ConsumeValueMultiSegment(first);
+                goto Done;
+            }
+            else
+            {
+                retVal = ConsumeNextTokenOrRollbackMultiSegment(first);
+                goto Done;
+            }
+
+            retVal = true;
+
+        Done:
+            return retVal;
+
+        ReadFirstToken:
+            retVal = ReadFirstTokenMultiSegment(first);
+            goto Done;
+        }
+
+        private bool ValidateStateAtEndOfData()
+        {
+            Debug.Assert(_isNotPrimitive && IsLastSpan);
+
+            if (_bitStack.CurrentDepth != 0)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ZeroDepthAtEnd);
+            }
+
+            if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && _tokenType == JsonTokenType.Comment)
+            {
+                return false;
+            }
+
+            if (_tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
+            }
+
+            return true;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HasMoreDataMultiSegment()
+        {
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (_isNotPrimitive && IsLastSpan)
+                {
+                    if (!ValidateStateAtEndOfData())
+                    {
+                        return false;
+                    }
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (_isNotPrimitive && IsLastSpan)
+                    {
+                        ValidateStateAtEndOfData();
+                    }
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // Unlike the parameter-less overload of HasMoreData, if there is no more data when this method is called, we know the JSON input is invalid.
+        // This is because, this method is only called after a ',' (i.e. we expect a value/property name) or after 
+        // a property name, which means it must be followed by a value.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HasMoreDataMultiSegment(ExceptionResource resource)
+        {
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, resource);
+                }
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, resource);
+                    }
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private bool GetNextSpan()
+        {
+            ReadOnlyMemory<byte> memory = default;
+            while (true)
+            {
+                SequencePosition copy = _currentPosition;
+                _currentPosition = _nextPosition;
+                bool noMoreData = !_sequence.TryGet(ref _nextPosition, out memory, advance: true);
+                if (noMoreData)
+                {
+                    _currentPosition = copy;
+                    _isLastSegment = true;
+                    return false;
+                }
+                if (memory.Length != 0)
+                {
+                    break;
+                }
+            }
+
+            if (_isFinalBlock)
+            {
+                _isLastSegment = !_sequence.TryGet(ref _nextPosition, out _, advance: false);
+            }
+
+            _buffer = memory.Span;
+            _totalConsumed += _consumed;
+            _consumed = 0;
+
+            return true;
+        }
+
+        private bool ReadFirstTokenMultiSegment(byte first)
+        {
+            if (first == JsonConstants.OpenBrace)
+            {
+                _bitStack.SetFirstBit();
+                _tokenType = JsonTokenType.StartObject;
+                _consumed++;
+                _bytePositionInLine++;
+                _inObject = true;
+                _isNotPrimitive = true;
+            }
+            else if (first == JsonConstants.OpenBracket)
+            {
+                _bitStack.ResetFirstBit();
+                _tokenType = JsonTokenType.StartArray;
+                _consumed++;
+                _bytePositionInLine++;
+                _isNotPrimitive = true;
+            }
+            else
+            {
+                if (JsonReaderHelper.IsDigit(first) || first == '-')
+                {
+                    if (!TryGetNumberMultiSegment(_buffer.Slice(_consumed), out int numberOfBytes))
+                    {
+                        return false;
+                    }
+                    _tokenType = JsonTokenType.Number;
+                    _consumed += numberOfBytes;
+                }
+                else if (!ConsumeValueMultiSegment(first))
+                {
+                    return false;
+                }
+
+                // Cannot use HasMoreData since the JSON payload contains a single, non-primitive value
+                // and hence must be handled differently.
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    goto SetIsNotPrimitiveAndReturnTrue;
+                }
+
+                if (_buffer[_consumed] <= JsonConstants.Space)
+                {
+                    SkipWhiteSpaceMultiSegment();
+                    if (_consumed >= (uint)_buffer.Length)
+                    {
+                        goto SetIsNotPrimitiveAndReturnTrue;
+                    }
+                }
+
+                if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
+                {
+                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
+                    {
+                        // This is necessary to avoid throwing when the user has 1 or more comments as the first token
+                        // OR if there is a comment after a single, non-primitive value.
+                        // In this mode, ConsumeValue consumes the comment and we need to return it as a token.
+                        // along with future comments in subsequeunt reads.
+                        if (_tokenType == JsonTokenType.Comment || _buffer[_consumed] == JsonConstants.Slash)
+                        {
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                        goto SetIsNotPrimitiveAndReturnTrue;
+                    }
+                }
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, _buffer[_consumed]);
+
+            SetIsNotPrimitiveAndReturnTrue:
+                if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
+                {
+                    _isNotPrimitive = true;
+                }
+                // Intentionally fall out of the if-block to return true
+            }
+            return true;
+        }
+
+        private void SkipWhiteSpaceMultiSegment()
+        {
+            while (true)
+            {
+                SkipWhiteSpace();
+
+                if (_consumed < _buffer.Length)
+                {
+                    break;
+                }
+
+                if (!GetNextSpan())
+                {
+                    break;
+                }
+            }
+        }
+
+        /// <summary>
+        /// This method contains the logic for processing the next value token and determining
+        /// what type of data it is.
+        /// </summary>
+        private bool ConsumeValueMultiSegment(byte marker)
+        {
+            while (true)
+            {
+                if (marker == JsonConstants.Quote)
+                {
+                    return ConsumeStringMultiSegment();
+                }
+                else if (marker == JsonConstants.OpenBrace)
+                {
+                    StartObject();
+                }
+                else if (marker == JsonConstants.OpenBracket)
+                {
+                    StartArray();
+                }
+                else if (JsonReaderHelper.IsDigit(marker) || marker == '-')
+                {
+                    return ConsumeNumberMultiSegment();
+                }
+                else if (marker == 'f')
+                {
+                    return ConsumeLiteralMultiSegment(JsonConstants.FalseValue, JsonTokenType.False);
+                }
+                else if (marker == 't')
+                {
+                    return ConsumeLiteralMultiSegment(JsonConstants.TrueValue, JsonTokenType.True);
+                }
+                else if (marker == 'n')
+                {
+                    return ConsumeLiteralMultiSegment(JsonConstants.NullValue, JsonTokenType.Null);
+                }
+                else
+                {
+                    switch (_readerOptions.CommentHandling)
+                    {
+                        case JsonCommentHandling.Disallow:
+                            break;
+                        case JsonCommentHandling.Allow:
+                            if (marker == JsonConstants.Slash)
+                            {
+                                return ConsumeCommentMultiSegment();
+                            }
+                            break;
+                        default:
+                            Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                            if (marker == JsonConstants.Slash)
+                            {
+                                if (SkipCommentMultiSegment())
+                                {
+                                    if (_consumed >= (uint)_buffer.Length)
+                                    {
+                                        if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
+                                        {
+                                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
+                                        }
+                                        if (!GetNextSpan())
+                                        {
+                                            if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
+                                            {
+                                                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
+                                            }
+                                            return false;
+                                        }
+                                    }
+
+                                    marker = _buffer[_consumed];
+
+                                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                                    if (marker <= JsonConstants.Space)
+                                    {
+                                        SkipWhiteSpaceMultiSegment();
+                                        if (!HasMoreDataMultiSegment())
+                                        {
+                                            return false;
+                                        }
+                                        marker = _buffer[_consumed];
+                                    }
+
+                                    // Skip comments and consume the actual JSON value.
+                                    continue;
+                                }
+                                return false;
+                            }
+                            break;
+                    }
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+                }
+                break;
+            }
+            return true;
+        }
+
+        // Consumes 'null', or 'true', or 'false'
+        private bool ConsumeLiteralMultiSegment(ReadOnlySpan<byte> literal, JsonTokenType tokenType)
+        {
+            ReadOnlySpan<byte> span = _buffer.Slice(_consumed);
+            Debug.Assert(span.Length > 0);
+            Debug.Assert(span[0] == 'n' || span[0] == 't' || span[0] == 'f');
+
+            int consumed = literal.Length;
+
+            if (!span.StartsWith(literal))
+            {
+                int prevConsumed = _consumed;
+                if (CheckLiteralMultiSegment(span, literal, out consumed))
+                {
+                    goto Done;
+                }
+                _consumed = prevConsumed;
+                return false;
+            }
+
+            ValueSpan = span.Slice(0, literal.Length);
+            HasValueSequence = false;
+        Done:
+            _tokenType = tokenType;
+            _consumed += consumed;
+            _bytePositionInLine += consumed;
+            return true;
+        }
+
+        private bool CheckLiteralMultiSegment(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal, out int consumed)
+        {
+            Debug.Assert(span.Length > 0 && span[0] == literal[0]);
+
+            Span<byte> readSoFar = stackalloc byte[literal.Length];
+            int written = 0;
+
+            long prevTotalConsumed = _totalConsumed;
+            if (span.Length >= literal.Length || IsLastSpan)
+            {
+                _bytePositionInLine += FindMismatch(span, literal);
+
+                int amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
+                span.Slice(0, amountToWrite).CopyTo(readSoFar);
+                written += amountToWrite;
+                goto Throw;
+            }
+            else
+            {
+                if (!literal.StartsWith(span))
+                {
+                    _bytePositionInLine += FindMismatch(span, literal);
+                    int amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
+                    span.Slice(0, amountToWrite).CopyTo(readSoFar);
+                    written += amountToWrite;
+                    goto Throw;
+                }
+
+                ReadOnlySpan<byte> leftToMatch = literal.Slice(span.Length);
+
+                SequencePosition startPosition = _currentPosition;
+                int startConsumed = _consumed;
+                int alreadyMatched = literal.Length - leftToMatch.Length;
+                while (true)
+                {
+                    _totalConsumed += alreadyMatched;
+                    _bytePositionInLine += alreadyMatched;
+                    if (!GetNextSpan())
+                    {
+                        _totalConsumed = prevTotalConsumed;
+                        consumed = default;
+                        if (IsLastSpan)
+                        {
+                            goto Throw;
+                        }
+                        return false;
+                    }
+
+                    int amountToWrite = Math.Min(span.Length, readSoFar.Length - written);
+                    span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written));
+                    written += amountToWrite;
+
+                    span = _buffer;
+
+                    if (span.StartsWith(leftToMatch))
+                    {
+                        HasValueSequence = true;
+                        SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
+                        SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + leftToMatch.Length);
+                        ValueSequence = _sequence.Slice(start, end);
+                        consumed = leftToMatch.Length;
+                        return true;
+                    }
+
+                    if (!leftToMatch.StartsWith(span))
+                    {
+                        _bytePositionInLine += FindMismatch(span, leftToMatch);
+
+                        amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
+                        span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written));
+                        written += amountToWrite;
+
+                        goto Throw;
+                    }
+
+                    leftToMatch = leftToMatch.Slice(span.Length);
+                    alreadyMatched = span.Length;
+                }
+            }
+        Throw:
+            _totalConsumed = prevTotalConsumed;
+            consumed = default;
+            throw GetInvalidLiteralMultiSegment(readSoFar.Slice(0, written).ToArray());
+        }
+
+        private int FindMismatch(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal)
+        {
+            Debug.Assert(span.Length > 0);
+
+            int indexOfFirstMismatch = 0;
+
+            int minLength = Math.Min(span.Length, literal.Length);
+
+            int i = 0;
+            for (; i < minLength; i++)
+            {
+                if (span[i] != literal[i])
+                {
+                    break;
+                }
+            }
+            indexOfFirstMismatch = i;
+
+            Debug.Assert(indexOfFirstMismatch >= 0 && indexOfFirstMismatch < literal.Length);
+
+            return indexOfFirstMismatch;
+        }
+
+        private JsonReaderException GetInvalidLiteralMultiSegment(ReadOnlySpan<byte> span)
+        {
+            byte firstByte = span[0];
+
+            ExceptionResource resource;
+            switch (firstByte)
+            {
+                case (byte)'t':
+                    resource = ExceptionResource.ExpectedTrue;
+                    break;
+                case (byte)'f':
+                    resource = ExceptionResource.ExpectedFalse;
+                    break;
+                default:
+                    Debug.Assert(firstByte == 'n');
+                    resource = ExceptionResource.ExpectedNull;
+                    break;
+            }
+            return ThrowHelper.GetJsonReaderException(ref this, resource, nextByte: default, bytes: span);
+        }
+
+        private bool ConsumeNumberMultiSegment()
+        {
+            if (!TryGetNumberMultiSegment(_buffer.Slice(_consumed), out int consumed))
+            {
+                return false;
+            }
+
+            _tokenType = JsonTokenType.Number;
+            _consumed += consumed;
+
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (!_isNotPrimitive)
+                {
+                    return true;
+                }
+                if (IsLastSpan || !GetNextSpan())
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed - 1]);
+                }
+            }
+
+            // TODO: https://github.com/dotnet/corefx/issues/33294
+            if (JsonConstants.Delimiters.IndexOf(_buffer[_consumed]) < 0)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed]);
+            }
+            return true;
+        }
+
+        private bool ConsumePropertyNameMultiSegment()
+        {
+            if (!ConsumeStringMultiSegment())
+            {
+                return false;
+            }
+
+            if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
+            {
+                return false;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
+            // Those cases are captured below where we only accept ':'.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpaceMultiSegment();
+                if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
+                {
+                    return false;
+                }
+                first = _buffer[_consumed];
+            }
+
+            // The next character must be a key / value seperator. Validate and skip.
+            if (first != JsonConstants.KeyValueSeperator)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedSeparatorAfterPropertyNameNotFound, first);
+            }
+
+            _consumed++;
+            _bytePositionInLine++;
+            _tokenType = JsonTokenType.PropertyName;
+            return true;
+        }
+
+        private bool ConsumeStringMultiSegment()
+        {
+            Debug.Assert(_buffer.Length >= _consumed + 1);
+            Debug.Assert(_buffer[_consumed] == JsonConstants.Quote);
+
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+
+            // Vectorized search for either quote, backslash, or any control character.
+            // If the first found byte is a quote, we have reached an end of string, and
+            // can avoid validation.
+            // Otherwise, in the uncommon case, iterate one character at a time and validate.
+            int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
+
+            if (idx >= 0)
+            {
+                byte foundByte = localBuffer[idx];
+                if (foundByte == JsonConstants.Quote)
+                {
+                    _bytePositionInLine += idx + 2; // Add 2 for the start and end quotes.
+                    ValueSpan = localBuffer.Slice(0, idx);
+                    HasValueSequence = false;
+                    _tokenType = JsonTokenType.String;
+                    _consumed += idx + 2;
+                    return true;
+                }
+                else
+                {
+                    return ConsumeStringAndValidateMultiSegment(localBuffer, idx);
+                }
+            }
+            else
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                }
+                return ConsumeStringNextSegment();
+            }
+        }
+
+        private bool ConsumeStringNextSegment()
+        {
+            SequencePosition startPosition = _currentPosition;
+            SequencePosition end = default;
+            int startConsumed = _consumed + 1;
+            HasValueSequence = true;
+            int leftOver = _buffer.Length - _consumed;
+
+            long prevTotalConsumed = _totalConsumed;
+            long prevPosition = _bytePositionInLine;
+
+            while (true)
+            {
+                if (!GetNextSpan())
+                {
+                    _totalConsumed = prevTotalConsumed;
+                    _bytePositionInLine = prevPosition;
+                    _consumed = startConsumed - 1;
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                    }
+                    return false;
+                }
+
+                //Create local copy to avoid bounds checks.
+                ReadOnlySpan<byte> localBuffer = _buffer;
+                int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
+
+                if (idx >= 0)
+                {
+                    byte foundByte = localBuffer[idx];
+                    if (foundByte == JsonConstants.Quote)
+                    {
+                        end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
+                        _bytePositionInLine += leftOver + idx + 1;  // Add 1 for the end quote of the string.
+                        _totalConsumed += leftOver;
+                        _consumed = idx + 1;    // Add 1 for the end quote of the string.
+                        break;
+                    }
+                    else
+                    {
+                        long prevLineNumber = _lineNumber;
+
+                        _bytePositionInLine += idx + 1; // Add 1 for the first quote
+
+                        bool nextCharEscaped = false;
+                        bool sawNewLine = false;
+                        while (true)
+                        {
+                        StartOfLoop:
+                            for (; idx < localBuffer.Length; idx++)
+                            {
+                                byte currentByte = localBuffer[idx];
+                                if (currentByte == JsonConstants.Quote)
+                                {
+                                    if (!nextCharEscaped)
+                                    {
+                                        goto Done;
+                                    }
+                                    nextCharEscaped = false;
+                                }
+                                else if (currentByte == JsonConstants.BackSlash)
+                                {
+                                    nextCharEscaped = !nextCharEscaped;
+                                }
+                                else if (nextCharEscaped)
+                                {
+                                    int index = JsonConstants.EscapableChars.IndexOf(currentByte);
+                                    if (index == -1)
+                                    {
+                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
+                                    }
+
+                                    if (currentByte == JsonConstants.Quote)
+                                    {
+                                        // Ignore an escaped quote.
+                                        // This is likely the most common case, so adding an explicit check
+                                        // to avoid doing the unnecessary checks below.
+                                    }
+                                    else if (currentByte == 'n')
+                                    {
+                                        // Escaped new line character
+                                        _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
+                                        _lineNumber++;
+                                        sawNewLine = true;
+                                    }
+                                    else if (currentByte == 'u')
+                                    {
+                                        // Expecting 4 hex digits to follow the escaped 'u'
+                                        _bytePositionInLine++;  // move past the 'u'
+
+                                        bool movedToNext = false;
+                                        int numberOfHexDigits = 3;
+                                        int j = idx + 1;
+                                        while (true)
+                                        {
+                                            for (; j < localBuffer.Length; j++)
+                                            {
+                                                byte nextByte = localBuffer[j];
+                                                if (!JsonReaderHelper.IsHexDigit(nextByte))
+                                                {
+                                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
+                                                }
+                                                if (j - idx > numberOfHexDigits)
+                                                {
+                                                    if (movedToNext)
+                                                    {
+                                                        nextCharEscaped = false;
+                                                        goto StartOfLoop;
+                                                    }
+                                                    goto DoneWithHex;
+                                                }
+                                                _bytePositionInLine++;
+                                            }
+
+                                            if (!GetNextSpan())
+                                            {
+                                                if (IsLastSpan)
+                                                {
+                                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                                                }
+
+                                                // We found less than 4 hex digits.
+                                                _lineNumber = prevLineNumber;
+                                                _bytePositionInLine = prevPosition;
+                                                return false;
+                                            }
+
+                                            _totalConsumed += localBuffer.Length;
+
+                                            localBuffer = _buffer;
+                                            idx = 0;
+                                            j = 0;
+                                            movedToNext = true;
+                                            numberOfHexDigits -= j - idx;
+                                        }
+
+                                    DoneWithHex:
+                                        idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
+                                    }
+                                    nextCharEscaped = false;
+                                }
+                                else if (currentByte < JsonConstants.Space)
+                                {
+                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
+                                }
+
+                                _bytePositionInLine++;
+                            }
+
+                            if (!GetNextSpan())
+                            {
+                                if (IsLastSpan)
+                                {
+                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                                }
+                                _lineNumber = prevLineNumber;
+                                _bytePositionInLine = prevPosition;
+                                return false;
+                            }
+
+                            _totalConsumed += localBuffer.Length;
+                            localBuffer = _buffer;
+                            idx = 0;
+                        }
+
+                    Done:
+                        _bytePositionInLine += sawNewLine ? leftOver + idx : leftOver + idx + 1;  // Add 1 for the end quote of the string.
+                        _consumed = idx + 1;    // Add 1 for the end quote of the string.
+                        _totalConsumed += leftOver;
+                        end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
+                        break;
+                    }
+                }
+
+                _totalConsumed += localBuffer.Length;
+                _bytePositionInLine += localBuffer.Length;
+            }
+
+            SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
+            ValueSequence = _sequence.Slice(start, end);
+            _tokenType = JsonTokenType.String;
+            return true;
+        }
+
+        // Found a backslash or control characters which are considered invalid within a string.
+        // Search through the rest of the string one byte at a time.
+        // https://tools.ietf.org/html/rfc8259#section-7
+        private bool ConsumeStringAndValidateMultiSegment(ReadOnlySpan<byte> data, int idx)
+        {
+            Debug.Assert(idx >= 0 && idx < data.Length);
+            Debug.Assert(data[idx] != JsonConstants.Quote);
+            Debug.Assert(data[idx] == JsonConstants.BackSlash || data[idx] < JsonConstants.Space);
+
+            SequencePosition startPosition = _currentPosition;
+            SequencePosition end = default;
+            int startConsumed = _consumed + 1;
+            HasValueSequence = false;
+            int leftOver = _buffer.Length - idx;
+            int leftOverFromConsumed = _buffer.Length - _consumed;
+
+            long prevTotalConsumed = _totalConsumed;
+            long prevLineBytePosition = _bytePositionInLine;
+            long prevLineNumber = _lineNumber;
+
+            _bytePositionInLine += idx + 1; // Add 1 for the first quote
+
+            bool nextCharEscaped = false;
+            while (true)
+            {
+            StartOfLoop:
+                for (; idx < data.Length; idx++)
+                {
+                    byte currentByte = data[idx];
+                    if (currentByte == JsonConstants.Quote)
+                    {
+                        if (!nextCharEscaped)
+                        {
+                            goto Done;
+                        }
+                        nextCharEscaped = false;
+                    }
+                    else if (currentByte == JsonConstants.BackSlash)
+                    {
+                        nextCharEscaped = !nextCharEscaped;
+                    }
+                    else if (nextCharEscaped)
+                    {
+                        int index = JsonConstants.EscapableChars.IndexOf(currentByte);
+                        if (index == -1)
+                        {
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
+                        }
+
+                        if (currentByte == JsonConstants.Quote)
+                        {
+                            // Ignore an escaped quote.
+                            // This is likely the most common case, so adding an explicit check
+                            // to avoid doing the unnecessary checks below.
+                        }
+                        else if (currentByte == 'n')
+                        {
+                            // Escaped new line character
+                            _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
+                            _lineNumber++;
+                        }
+                        else if (currentByte == 'u')
+                        {
+                            // Expecting 4 hex digits to follow the escaped 'u'
+                            _bytePositionInLine++;  // move past the 'u'
+
+                            bool movedToNext = false;
+                            int numberOfHexDigits = 3;
+                            int j = idx + 1;
+                            while (true)
+                            {
+                                for (; j < data.Length; j++)
+                                {
+                                    byte nextByte = data[j];
+                                    if (!JsonReaderHelper.IsHexDigit(nextByte))
+                                    {
+                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
+                                    }
+                                    if (j - idx > numberOfHexDigits)
+                                    {
+                                        if (movedToNext)
+                                        {
+                                            nextCharEscaped = false;
+                                            goto StartOfLoop;
+                                        }
+                                        goto DoneWithHex;
+                                    }
+                                    _bytePositionInLine++;
+                                }
+
+                                if (!GetNextSpan())
+                                {
+                                    if (IsLastSpan)
+                                    {
+                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                                    }
+
+                                    // We found less than 4 hex digits.
+                                    _lineNumber = prevLineNumber;
+                                    _bytePositionInLine = prevLineBytePosition;
+                                    return false;
+                                }
+
+                                // Do not add the left over for the first segment to total consumed
+                                if (HasValueSequence)
+                                {
+                                    _totalConsumed += data.Length;
+                                }
+
+                                data = _buffer;
+                                idx = 0;
+                                j = 0;
+                                HasValueSequence = true;
+                                movedToNext = true;
+                                numberOfHexDigits -= j - idx;
+                            }
+
+                        DoneWithHex:
+                            idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
+                        }
+                        nextCharEscaped = false;
+                    }
+                    else if (currentByte < JsonConstants.Space)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
+                    }
+
+                    _bytePositionInLine++;
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                    }
+                    _lineNumber = prevLineNumber;
+                    _bytePositionInLine = prevLineBytePosition;
+                    return false;
+                }
+
+                // Do not add the left over for the first segment to total consumed
+                if (HasValueSequence)
+                {
+                    _totalConsumed += data.Length;
+                }
+
+                data = _buffer;
+                idx = 0;
+                HasValueSequence = true;
+            }
+
+        Done:
+            if (HasValueSequence)
+            {
+                _bytePositionInLine += leftOver + idx + 1;  // Add 1 for the end quote of the string.
+                _consumed = idx + 1;    // Add 1 for the end quote of the string.
+                _totalConsumed += leftOverFromConsumed;
+                end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
+                SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
+                ValueSequence = _sequence.Slice(start, end);
+            }
+            else
+            {
+                _bytePositionInLine++;  // Add 1 for the end quote
+                _consumed += idx + 2;
+                ValueSpan = data.Slice(0, idx);
+            }
+            _tokenType = JsonTokenType.String;
+            return true;
+        }
+
+        // https://tools.ietf.org/html/rfc7159#section-6
+        private bool TryGetNumberMultiSegment(ReadOnlySpan<byte> data, out int consumed)
+        {
+            // TODO: https://github.com/dotnet/corefx/issues/33294
+            Debug.Assert(data.Length > 0);
+
+            _numberFormat = default;
+            SequencePosition startPosition = _currentPosition;
+            int startConsumed = _consumed;
+            consumed = 0;
+            long prevTotalConsumed = _totalConsumed;
+            long prevPosition = _bytePositionInLine;
+            int i = 0;
+
+            ConsumeNumberResult signResult = ConsumeNegativeSignMultiSegment(ref data, ref i);
+            if (signResult == ConsumeNumberResult.NeedMoreData)
+            {
+                _totalConsumed = prevTotalConsumed;
+                _bytePositionInLine = prevPosition;
+                _consumed = startConsumed;
+                return false;
+            }
+
+            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
+
+            byte nextByte = data[i];
+            Debug.Assert(nextByte >= '0' && nextByte <= '9');
+
+            if (nextByte == '0')
+            {
+                ConsumeNumberResult result = ConsumeZeroMultiSegment(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    _totalConsumed = prevTotalConsumed;
+                    _bytePositionInLine = prevPosition;
+                    _consumed = startConsumed;
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+            }
+            else
+            {
+                ConsumeNumberResult result = ConsumeIntegerDigitsMultiSegment(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    _totalConsumed = prevTotalConsumed;
+                    _bytePositionInLine = prevPosition;
+                    _consumed = startConsumed;
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+                if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
+                }
+            }
+
+            Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');
+
+            if (nextByte == '.')
+            {
+                i++;
+                _bytePositionInLine++;
+                ConsumeNumberResult result = ConsumeDecimalDigitsMultiSegment(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    _totalConsumed = prevTotalConsumed;
+                    _bytePositionInLine = prevPosition;
+                    _consumed = startConsumed;
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+                if (nextByte != 'E' && nextByte != 'e')
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
+                }
+            }
+
+            Debug.Assert(nextByte == 'E' || nextByte == 'e');
+            i++;
+            _numberFormat = 'e';
+            _bytePositionInLine++;
+
+            signResult = ConsumeSignMultiSegment(ref data, ref i);
+            if (signResult == ConsumeNumberResult.NeedMoreData)
+            {
+                _totalConsumed = prevTotalConsumed;
+                _bytePositionInLine = prevPosition;
+                _consumed = startConsumed;
+                return false;
+            }
+
+            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
+
+            i++;
+            _bytePositionInLine++;
+            ConsumeNumberResult resultExponent = ConsumeIntegerDigitsMultiSegment(ref data, ref i);
+            if (resultExponent == ConsumeNumberResult.NeedMoreData)
+            {
+                _totalConsumed = prevTotalConsumed;
+                _bytePositionInLine = prevPosition;
+                _consumed = startConsumed;
+                return false;
+            }
+            if (resultExponent == ConsumeNumberResult.Success)
+            {
+                goto Done;
+            }
+
+            Debug.Assert(resultExponent == ConsumeNumberResult.OperationIncomplete);
+
+            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, data[i]);
+
+        Done:
+            if (HasValueSequence)
+            {
+                SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
+                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + i);
+                ValueSequence = _sequence.Slice(start, end);
+                consumed = i;
+            }
+            else
+            {
+                ValueSpan = data.Slice(0, i);
+                consumed = i;
+            }
+            return true;
+        }
+
+        private ConsumeNumberResult ConsumeNegativeSignMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            byte nextByte = data[i];
+
+            if (nextByte == '-')
+            {
+                i++;
+                _bytePositionInLine++;
+                if (i >= data.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                        }
+                        return ConsumeNumberResult.NeedMoreData;
+                    }
+                    _totalConsumed++;
+                    HasValueSequence = true;
+                    i = 0;
+                    data = _buffer;
+                }
+
+                nextByte = data[i];
+                if (!JsonReaderHelper.IsDigit(nextByte))
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
+                }
+            }
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeZeroMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            Debug.Assert(data[i] == (byte)'0');
+            i++;
+            _bytePositionInLine++;
+            byte nextByte = default;
+            if (i < data.Length)
+            {
+                nextByte = data[i];
+                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
+                {
+                    return ConsumeNumberResult.Success;
+                }
+            }
+            else
+            {
+                if (IsLastSpan)
+                {
+                    // A payload containing a single value: "0" is valid
+                    // If we are v with multi-value JSON,
+                    // ConsumeNumber will validate that we have a delimiter following the "0".
+                    return ConsumeNumberResult.Success;
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        return ConsumeNumberResult.Success;
+                    }
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+
+                _totalConsumed++;
+                HasValueSequence = true;
+                i = 0;
+                data = _buffer;
+                nextByte = data[i];
+                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
+                {
+                    return ConsumeNumberResult.Success;
+                }
+            }
+            nextByte = data[i];
+            if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeIntegerDigitsMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            byte nextByte = default;
+            int counter = 0;
+            for (; i < data.Length; i++)
+            {
+                nextByte = data[i];
+                if (!JsonReaderHelper.IsDigit(nextByte))
+                {
+                    break;
+                }
+                counter++;
+            }
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    // A payload containing a single value of integers (e.g. "12") is valid
+                    // If we are dealing with multi-value JSON,
+                    // ConsumeNumber will validate that we have a delimiter following the integer.
+                    _bytePositionInLine += counter;
+                    return ConsumeNumberResult.Success;
+                }
+
+                while (true)
+                {
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            _bytePositionInLine += counter;
+                            return ConsumeNumberResult.Success;
+                        }
+                        return ConsumeNumberResult.NeedMoreData;
+                    }
+
+                    _totalConsumed += i;
+                    _bytePositionInLine += counter;
+                    counter = 0;
+                    HasValueSequence = true;
+                    i = 0;
+                    data = _buffer;
+                    for (; i < data.Length; i++)
+                    {
+                        nextByte = data[i];
+                        if (!JsonReaderHelper.IsDigit(nextByte))
+                        {
+                            break;
+                        }
+                    }
+                    _bytePositionInLine += i;
+                    if (i >= data.Length)
+                    {
+                        if (IsLastSpan)
+                        {
+                            return ConsumeNumberResult.Success;
+                        }
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+
+            }
+            else
+            {
+                _bytePositionInLine += counter;
+            }
+
+            if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
+            {
+                return ConsumeNumberResult.Success;
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeDecimalDigitsMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                }
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+                _totalConsumed += i;
+                HasValueSequence = true;
+                i = 0;
+                data = _buffer;
+            }
+            byte nextByte = data[i];
+            if (!JsonReaderHelper.IsDigit(nextByte))
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterDecimal, nextByte);
+            }
+            i++;
+            _bytePositionInLine++;
+            return ConsumeIntegerDigitsMultiSegment(ref data, ref i);
+        }
+
+        private ConsumeNumberResult ConsumeSignMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+                HasValueSequence = true;
+                i = 0;
+                data = _buffer;
+            }
+
+            byte nextByte = data[i];
+            if (nextByte == '+' || nextByte == '-')
+            {
+                i++;
+                _bytePositionInLine++;
+                if (i >= data.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                        }
+                        return ConsumeNumberResult.NeedMoreData;
+                    }
+                    _totalConsumed++;
+                    HasValueSequence = true;
+                    i = 0;
+                    data = _buffer;
+                }
+                nextByte = data[i];
+            }
+
+            if (!JsonReaderHelper.IsDigit(nextByte))
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private bool ConsumeNextTokenOrRollbackMultiSegment(byte marker)
+        {
+            long prevTotalConsumed = _totalConsumed;
+            int prevConsumed = _consumed;
+            long prevPosition = _bytePositionInLine;
+            long prevLineNumber = _lineNumber;
+            JsonTokenType prevTokenType = _tokenType;
+            ConsumeTokenResult result = ConsumeNextTokenMultiSegment(marker);
+            if (result == ConsumeTokenResult.Success)
+            {
+                return true;
+            }
+            if (result == ConsumeTokenResult.NotEnoughDataRollBackState)
+            {
+                _consumed = prevConsumed;
+                _tokenType = prevTokenType;
+                _bytePositionInLine = prevPosition;
+                _lineNumber = prevLineNumber;
+                _totalConsumed = prevTotalConsumed;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// This method consumes the next token regardless of whether we are inside an object or an array.
+        /// For an object, it reads the next property name token. For an array, it just reads the next value.
+        /// </summary>
+        private ConsumeTokenResult ConsumeNextTokenMultiSegment(byte marker)
+        {
+            if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
+            {
+                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
+                {
+                    if (marker == JsonConstants.Slash)
+                    {
+                        return ConsumeCommentMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    if (_tokenType == JsonTokenType.Comment)
+                    {
+                        return ConsumeNextTokenFromLastNonCommentTokenMultiSegment();
+                    }
+                }
+                else
+                {
+                    Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                    return ConsumeNextTokenUntilAfterAllCommentsAreSkippedMultiSegment(marker);
+                }
+            }
+
+            if (!_isNotPrimitive)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
+            }
+
+            if (marker == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            _consumed--;
+                            _bytePositionInLine--;
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                        }
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                }
+                byte first = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (first <= JsonConstants.Space)
+                {
+                    SkipWhiteSpaceMultiSegment();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    first = _buffer[_consumed];
+                }
+
+                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash)
+                {
+                    return ConsumeCommentMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+
+                if (_inObject)
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+                    return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                else
+                {
+                    return ConsumeValueMultiSegment(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+            }
+            else if (marker == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (marker == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
+            }
+            return ConsumeTokenResult.Success;
+        }
+
+        private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment()
+        {
+            if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType))
+            {
+                _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray;
+            }
+            else
+            {
+                _tokenType = _previousTokenType;
+            }
+
+            Debug.Assert(_tokenType != JsonTokenType.Comment);
+
+            if (!HasMoreDataMultiSegment())
+            {
+                goto RollBack;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpaceMultiSegment();
+                if (!HasMoreDataMultiSegment())
+                {
+                    goto RollBack;
+                }
+                first = _buffer[_consumed];
+            }
+
+            if (!_isNotPrimitive && _tokenType != JsonTokenType.None)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, first);
+            }
+
+            Debug.Assert(first != JsonConstants.Slash);
+
+            if (first == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            _consumed--;
+                            _bytePositionInLine--;
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                        }
+                        goto RollBack;
+                    }
+                }
+                first = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (first <= JsonConstants.Space)
+                {
+                    SkipWhiteSpaceMultiSegment();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        goto RollBack;
+                    }
+                    first = _buffer[_consumed];
+                }
+
+                if (_inObject)
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+                    if (ConsumePropertyNameMultiSegment())
+                    {
+                        goto Done;
+                    }
+                    else
+                    {
+                        goto RollBack;
+                    }
+                }
+                else
+                {
+                    if (ConsumeValueMultiSegment(first))
+                    {
+                        goto Done;
+                    }
+                    else
+                    {
+                        goto RollBack;
+                    }
+                }
+            }
+            else if (first == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (first == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else if (_tokenType == JsonTokenType.None)
+            {
+                if (ReadFirstTokenMultiSegment(first))
+                {
+                    goto Done;
+                }
+                else
+                {
+                    goto RollBack;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (first == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+
+                    long prevTotalConsumed = _totalConsumed;
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    if (!ConsumePropertyNameMultiSegment())
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                        _totalConsumed = prevTotalConsumed;
+                        goto RollBack;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (first == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    if (!ConsumeValueMultiSegment(first))
+                    {
+                        goto RollBack;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                if (!ConsumeValueMultiSegment(first))
+                {
+                    goto RollBack;
+                }
+                goto Done;
+            }
+            else
+            {
+                goto RollBack;
+            }
+
+        Done:
+            return ConsumeTokenResult.Success;
+
+        RollBack:
+            return ConsumeTokenResult.NotEnoughDataRollBackState;
+        }
+
+        private bool SkipAllCommentsMultiSegment(ref byte marker)
+        {
+            while (marker == JsonConstants.Slash)
+            {
+                if (SkipCommentMultiSegment())
+                {
+                    if (!HasMoreDataMultiSegment())
+                    {
+                        goto IncompleteNoRollback;
+                    }
+
+                    marker = _buffer[_consumed];
+
+                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                    if (marker <= JsonConstants.Space)
+                    {
+                        SkipWhiteSpaceMultiSegment();
+                        if (!HasMoreDataMultiSegment())
+                        {
+                            goto IncompleteNoRollback;
+                        }
+                        marker = _buffer[_consumed];
+                    }
+                }
+                else
+                {
+                    goto IncompleteNoRollback;
+                }
+            }
+            return true;
+
+        IncompleteNoRollback:
+            return false;
+        }
+
+        private bool SkipAllCommentsMultiSegment(ref byte marker, ExceptionResource resource)
+        {
+            while (marker == JsonConstants.Slash)
+            {
+                if (SkipCommentMultiSegment())
+                {
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreDataMultiSegment(resource))
+                    {
+                        goto IncompleteRollback;
+                    }
+
+                    marker = _buffer[_consumed];
+
+                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                    if (marker <= JsonConstants.Space)
+                    {
+                        SkipWhiteSpaceMultiSegment();
+                        // The next character must be a start of a property name or value.
+                        if (!HasMoreDataMultiSegment(resource))
+                        {
+                            goto IncompleteRollback;
+                        }
+                        marker = _buffer[_consumed];
+                    }
+                }
+                else
+                {
+                    goto IncompleteRollback;
+                }
+            }
+            return true;
+
+        IncompleteRollback:
+            return false;
+        }
+
+        private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkippedMultiSegment(byte marker)
+        {
+            if (!SkipAllCommentsMultiSegment(ref marker))
+            {
+                goto IncompleteNoRollback;
+            }
+
+            if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (marker == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (marker != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
+                    }
+
+                    long prevTotalConsumed = _totalConsumed;
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    if (!ConsumePropertyNameMultiSegment())
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                        _totalConsumed = prevTotalConsumed;
+                        goto IncompleteNoRollback;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (marker == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    if (!ConsumeValueMultiSegment(marker))
+                    {
+                        goto IncompleteNoRollback;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                if (!ConsumeValueMultiSegment(marker))
+                {
+                    goto IncompleteNoRollback;
+                }
+                goto Done;
+            }
+            else if (!_isNotPrimitive)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
+            }
+            else if (marker == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    if (!GetNextSpan())
+                    {
+                        if (IsLastSpan)
+                        {
+                            _consumed--;
+                            _bytePositionInLine--;
+                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                        }
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                }
+                marker = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (marker <= JsonConstants.Space)
+                {
+                    SkipWhiteSpaceMultiSegment();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    marker = _buffer[_consumed];
+                }
+
+                if (!SkipAllCommentsMultiSegment(ref marker, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                {
+                    goto IncompleteRollback;
+                }
+
+                if (_inObject)
+                {
+                    if (marker != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
+                    }
+                    return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                else
+                {
+                    return ConsumeValueMultiSegment(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+            }
+            else if (marker == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (marker == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
+            }
+
+        Done:
+            return ConsumeTokenResult.Success;
+        IncompleteNoRollback:
+            return ConsumeTokenResult.IncompleteNoRollBackNecessary;
+        IncompleteRollback:
+            return ConsumeTokenResult.NotEnoughDataRollBackState;
+        }
+
+        private bool SkipCommentMultiSegment()
+        {
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+            int leftOver = 2;
+
+            if (localBuffer.Length == 0)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+                    }
+                    return false;
+                }
+
+                _totalConsumed++;
+                _bytePositionInLine++;
+                localBuffer = _buffer;
+                leftOver = 1;
+            }
+
+            byte marker = localBuffer[0];
+
+            if (marker == JsonConstants.Slash)
+            {
+                return SkipSingleLineCommentMultiSegment(localBuffer.Slice(1), leftOver);
+            }
+
+            if (marker != JsonConstants.Asterisk)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+            }
+
+            return SkipMultiLineCommentMultiSegment(localBuffer.Slice(1), leftOver);
+        }
+
+        private bool SkipSingleLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver)
+        {
+            long prevTotalConsumed = _totalConsumed;
+            int idx;
+            do
+            {
+                // TODO: https://github.com/dotnet/corefx/issues/33293
+                idx = localBuffer.IndexOf(JsonConstants.LineFeed);
+                if (idx == -1)
+                {
+                    if (IsLastSpan)
+                    {
+                        idx = localBuffer.Length;
+                        // Assume everything on this line is a comment and there is no more data.
+                        _bytePositionInLine += 2 + localBuffer.Length;
+                        goto Done;
+                    }
+
+                    if (!GetNextSpan())
+                    {
+                        _totalConsumed = prevTotalConsumed;
+                        return false;
+                    }
+                    _totalConsumed += localBuffer.Length + leftOver;
+                    leftOver = 0;
+                    localBuffer = _buffer;
+                }
+            } while (idx == -1);
+
+            idx++;
+            _bytePositionInLine = 0;
+            _lineNumber++;
+        Done:
+            _consumed += leftOver + idx;
+            return true;
+        }
+
+        private bool SkipMultiLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver)
+        {
+            long prevTotalConsumed = _totalConsumed;
+            int i;
+            bool lastAsterisk = false;
+            while (true)
+            {
+                i = 0;
+                for (; i < localBuffer.Length; i++)
+                {
+                    byte nextByte = localBuffer[i];
+
+                    if (nextByte == JsonConstants.Slash && lastAsterisk)
+                    {
+                        goto Done;
+                    }
+
+                    if (nextByte == JsonConstants.Asterisk)
+                    {
+                        i++;
+                        lastAsterisk = true;
+                        if (i < localBuffer.Length)
+                        {
+                            if (localBuffer[i] == JsonConstants.Slash)
+                            {
+                                goto Done;
+                            }
+                        }
+                        else
+                        {
+                            if (!GetNextSpan())
+                            {
+                                _totalConsumed = prevTotalConsumed;
+                                return false;
+                            }
+                            _totalConsumed += localBuffer.Length + leftOver;
+                            _bytePositionInLine += localBuffer.Length + leftOver;
+                            leftOver = 0;
+                            localBuffer = _buffer;
+                            i = 0;
+                            if (localBuffer[i] == JsonConstants.Slash)
+                            {
+                                goto Done;
+                            }
+                            break;
+                        }
+                    }
+                    else if (nextByte == JsonConstants.LineFeed)
+                    {
+                        _bytePositionInLine = 0;
+                        _lineNumber++;
+                        lastAsterisk = false;
+                    }
+                    else
+                    {
+                        lastAsterisk = false;
+                    }
+                }
+                if (i == localBuffer.Length)
+                {
+                    if (!GetNextSpan())
+                    {
+                        _totalConsumed = prevTotalConsumed;
+                        return false;
+                    }
+                    _totalConsumed += localBuffer.Length + leftOver;
+                    _bytePositionInLine += localBuffer.Length + leftOver;
+                    leftOver = 0;
+                    localBuffer = _buffer;
+                }
+            }
+
+        Done:
+            _consumed += i + 1 + leftOver;
+            _bytePositionInLine += i + 1 + leftOver;
+            return true;
+        }
+
+        private bool ConsumeCommentMultiSegment()
+        {
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+            int leftOver = 2;
+
+            SequencePosition start = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + _consumed);
+            if (localBuffer.Length == 0)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+                }
+
+                if (!GetNextSpan())
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+                    }
+                    return false;
+                }
+
+                _totalConsumed++;
+                _bytePositionInLine++;
+                localBuffer = _buffer;
+                leftOver = 1;
+            }
+
+            byte marker = localBuffer[0];
+
+            if (marker == JsonConstants.Slash)
+            {
+                return ConsumeSingleLineCommentMultiSegment(localBuffer.Slice(1), leftOver, start, _consumed);
+            }
+
+            if (marker != JsonConstants.Asterisk)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+            }
+
+            return ConsumeMultiLineCommentMultiSegment(localBuffer.Slice(1), leftOver, start, _consumed);
+        }
+
+        private bool ConsumeSingleLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver, SequencePosition start, int previousConsumed)
+        {
+            long prevTotalConsumed = _totalConsumed;
+            int idx;
+            do
+            {
+                // TODO: https://github.com/dotnet/corefx/issues/33293
+                idx = localBuffer.IndexOf(JsonConstants.LineFeed);
+                if (idx == -1)
+                {
+                    if (IsLastSpan)
+                    {
+                        idx = localBuffer.Length;
+                        // Assume everything on this line is a comment and there is no more data.
+                        _bytePositionInLine += 2 + localBuffer.Length;
+                        goto Done;
+                    }
+
+                    if (!GetNextSpan())
+                    {
+                        _totalConsumed = prevTotalConsumed;
+                        return false;
+                    }
+                    HasValueSequence = true;
+                    _totalConsumed += localBuffer.Length + leftOver;
+                    leftOver = 0;
+                    localBuffer = _buffer;
+                }
+            } while (idx == -1);
+
+            idx++;
+            _bytePositionInLine = 0;
+            _lineNumber++;
+
+        Done:
+            if (HasValueSequence)
+            {
+                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
+                ReadOnlySequence<byte> commentSequence = _sequence.Slice(start, end);
+                if (commentSequence.IsSingleSegment)
+                {
+                    ValueSpan = commentSequence.First.Span;
+                    HasValueSequence = false;
+                }
+                else
+                {
+                    ValueSequence = commentSequence;
+                }
+            }
+            else
+            {
+                ValueSpan = _buffer.Slice(previousConsumed, idx + 2);   // Include the double slash and potential line feed at the end of the comment as part of it.
+            }
+
+            if (_tokenType != JsonTokenType.Comment)
+            {
+                _previousTokenType = _tokenType;
+            }
+            _tokenType = JsonTokenType.Comment;
+            _consumed += leftOver + idx;
+            return true;
+        }
+
+        private bool ConsumeMultiLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver, SequencePosition start, int previousConsumed)
+        {
+            long prevTotalConsumed = _totalConsumed;
+            int i;
+            int lastLineFeedIndex;
+            bool lastAsterisk = false;
+            while (true)
+            {
+                i = 0;
+                lastLineFeedIndex = -1;
+                for (; i < localBuffer.Length; i++)
+                {
+                    byte nextByte = localBuffer[i];
+
+                    if (nextByte == JsonConstants.Slash && lastAsterisk)
+                    {
+                        goto Done;
+                    }
+
+                    if (nextByte == JsonConstants.Asterisk)
+                    {
+                        i++;
+                        lastAsterisk = true;
+                        if (i < localBuffer.Length)
+                        {
+                            if (localBuffer[i] == JsonConstants.Slash)
+                            {
+                                goto Done;
+                            }
+                        }
+                        else
+                        {
+                            if (!GetNextSpan())
+                            {
+                                _totalConsumed = prevTotalConsumed;
+                                return false;
+                            }
+                            HasValueSequence = true;
+                            _totalConsumed += localBuffer.Length + leftOver;
+
+                            if (lastLineFeedIndex == -1)
+                            {
+                                _bytePositionInLine += localBuffer.Length + leftOver;
+                            }
+                            else
+                            {
+                                _bytePositionInLine += i - lastLineFeedIndex - 1;
+                            }
+                            lastLineFeedIndex = -1;
+                            leftOver = 0;
+                            localBuffer = _buffer;
+                            i = 0;
+                            if (localBuffer[i] == JsonConstants.Slash)
+                            {
+                                goto Done;
+                            }
+                            break;
+                        }
+                    }
+                    else if (nextByte == JsonConstants.LineFeed)
+                    {
+                        lastLineFeedIndex = i;
+                        _bytePositionInLine = 0;
+                        _lineNumber++;
+                        lastAsterisk = false;
+                    }
+                    else
+                    {
+                        lastAsterisk = false;
+                    }
+                }
+                if (i == localBuffer.Length)
+                {
+                    if (!GetNextSpan())
+                    {
+                        _totalConsumed = prevTotalConsumed;
+                        return false;
+                    }
+                    HasValueSequence = true;
+                    _totalConsumed += localBuffer.Length + leftOver;
+                    if (lastLineFeedIndex == -1)
+                    {
+                        _bytePositionInLine += localBuffer.Length + leftOver;
+                    }
+                    else
+                    {
+                        _bytePositionInLine += i - lastLineFeedIndex - 1;
+                    }
+                    lastLineFeedIndex = -1;
+                    leftOver = 0;
+                    localBuffer = _buffer;
+                }
+            }
+
+        Done:
+            if (HasValueSequence)
+            {
+                // Include the final slash at the end of the comment as part of it.
+                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + i + 1);
+                ReadOnlySequence<byte> commentSequence = _sequence.Slice(start, end);
+                if (commentSequence.IsSingleSegment)
+                {
+                    ValueSpan = commentSequence.First.Span;
+                    HasValueSequence = false;
+                }
+                else
+                {
+                    ValueSequence = commentSequence;
+                }
+            }
+            else
+            {
+
+                ValueSpan = _buffer.Slice(previousConsumed, i + 3); // Include the slash/asterisk and final slash at the end of the comment as part of it.
+            }
+
+            if (_tokenType != JsonTokenType.Comment)
+            {
+                _previousTokenType = _tokenType;
+            }
+            _tokenType = JsonTokenType.Comment;
+            _consumed += i + 1 + leftOver;
+            if (lastLineFeedIndex == -1)
+            {
+                _bytePositionInLine += i + 1 + leftOver;
+            }
+            else
+            {
+                _bytePositionInLine += i - lastLineFeedIndex;
+            }
+            return true;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
new file mode 100644 (file)
index 0000000..4fee0c9
--- /dev/null
@@ -0,0 +1,173 @@
+// 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 Utf8JsonReader
+    {
+        // Reject any invalid UTF-8 data rather than silently replacing.
+        private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+
+        /// <summary>
+        /// Reads the next JSON token value from the source transcoded as a <see cref="string"/>.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of the JSON token that is not a string
+        /// (i.e. other than <see cref="JsonTokenType.String"/> or <see cref="JsonTokenType.PropertyName"/>).
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        /// Thrown if invalid UTF-8 byte sequences are detected while transcoding.
+        /// </exception>
+        public string GetStringValue()
+        {
+            if (TokenType != JsonTokenType.String && TokenType != JsonTokenType.PropertyName)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+
+            // TODO: https://github.com/dotnet/corefx/issues/33292
+            return s_utf8Encoding.GetString(span);
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source as a <see cref="bool"/>.
+        /// Returns true if the TokenType is JsonTokenType.True and false if the TokenType is JsonTokenType.False.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a boolean (i.e. <see cref="JsonTokenType.True"/> or <see cref="JsonTokenType.False"/>).
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool GetBooleanValue()
+        {
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+
+            if (TokenType == JsonTokenType.True)
+            {
+                Debug.Assert(span.Length == 4);
+                return true;
+            }
+            else if (TokenType == JsonTokenType.False)
+            {
+                Debug.Assert(span.Length == 5);
+                return false;
+            }
+            else
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedBoolean(TokenType);
+            }
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source and parses it to a <see cref="int"/>.
+        /// Returns true if the entire UTF-8 encoded token value can be successfully 
+        /// parsed to a <see cref="int"/> value.
+        /// Returns false otherwise.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool TryGetInt32Value(out int value)
+        {
+            if (TokenType != JsonTokenType.Number)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed) && span.Length == bytesConsumed;
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source and parses it to a <see cref="long"/>.
+        /// Returns true if the entire UTF-8 encoded token value can be successfully 
+        /// parsed to a <see cref="long"/> value.
+        /// Returns false otherwise.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool TryGetInt64Value(out long value)
+        {
+            if (TokenType != JsonTokenType.Number)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed) && span.Length == bytesConsumed;
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source and parses it to a <see cref="float"/>.
+        /// Returns true if the entire UTF-8 encoded token value can be successfully 
+        /// parsed to a <see cref="float"/> value.
+        /// Returns false otherwise.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool TryGetSingleValue(out float value)
+        {
+            if (TokenType != JsonTokenType.Number)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source and parses it to a <see cref="double"/>.
+        /// Returns true if the entire UTF-8 encoded token value can be successfully 
+        /// parsed to a <see cref="double"/> value.
+        /// Returns false otherwise.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool TryGetDoubleValue(out double value)
+        {
+            if (TokenType != JsonTokenType.Number)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
+        }
+
+        /// <summary>
+        /// Reads the next JSON token value from the source and parses it to a <see cref="decimal"/>.
+        /// Returns true if the entire UTF-8 encoded token value can be successfully 
+        /// parsed to a <see cref="decimal"/> value.
+        /// Returns false otherwise.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
+        /// <seealso cref="TokenType" />
+        /// </exception>
+        public bool TryGetDecimalValue(out decimal value)
+        {
+            if (TokenType != JsonTokenType.Number)
+            {
+                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
+            }
+
+            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs
new file mode 100644 (file)
index 0000000..0b60c3f
--- /dev/null
@@ -0,0 +1,1820 @@
+// 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, read-only access to the UTF-8 encoded JSON text.
+    /// It processes the text sequentially with no caching and adheres strictly to the JSON RFC
+    /// by default (https://tools.ietf.org/html/rfc8259). When it encounters invalid JSON, it throws
+    /// a JsonReaderException with basic error information like line number and byte position on the line.
+    /// Since this type is a ref struct, it does not directly support async. However, it does provide
+    /// support for reentrancy to read incomplete data, and continue reading once more data is presented.
+    /// To be able to set max depth while reading OR allow skipping comments, create an instance of 
+    /// <see cref="JsonReaderState"/> and pass that in to the reader.
+    /// </summary>
+    public ref partial struct Utf8JsonReader
+    {
+        private ReadOnlySpan<byte> _buffer;
+
+        private bool _isFinalBlock;
+
+        private long _lineNumber;
+        private long _bytePositionInLine;
+        private int _consumed;
+        private int _maxDepth;
+        private bool _inObject;
+        private bool _isNotPrimitive;
+        private char _numberFormat;
+        private JsonTokenType _tokenType;
+        private JsonTokenType _previousTokenType;
+        private JsonReaderOptions _readerOptions;
+        private BitStack _bitStack;
+
+        private long _totalConsumed;
+        private bool _isLastSegment;
+        private readonly bool _isSingleSegment;
+
+        private SequencePosition _nextPosition;
+        private SequencePosition _currentPosition;
+        private ReadOnlySequence<byte> _sequence;
+
+        private bool IsLastSpan => _isFinalBlock && (_isSingleSegment || _isLastSegment);
+
+        /// <summary>
+        /// Gets the value of the last processed token as a ReadOnlySpan&lt;byte&gt; slice
+        /// of the input payload. If the JSON is provided within a ReadOnlySequence&lt;byte&gt;
+        /// and the slice that represents the token value fits in a single segment, then
+        /// <see cref="ValueSpan"/> will contain the sliced value since it can be represented as a span.
+        /// Otherwise, the <see cref="ValueSequence"/> will contain the token value.
+        /// </summary>
+        /// <remarks>
+        /// If <see cref="HasValueSequence"/> is true, <see cref="ValueSpan"/> contains useless data, likely for
+        /// a previous single-segment token. Therefore, only access <see cref="ValueSpan"/> if <see cref="HasValueSequence"/> is false.
+        /// Otherwise, the token value must be accessed from <see cref="ValueSequence"/>.
+        /// </remarks>
+        public ReadOnlySpan<byte> ValueSpan { get; private set; }
+
+        /// <summary>
+        /// Returns the total amount of bytes consumed by the <see cref="Utf8JsonReader"/> so far
+        /// for the current instance of the <see cref="Utf8JsonReader"/> with the given UTF-8 encoded input text.
+        /// </summary>
+        public long BytesConsumed => _totalConsumed + _consumed;
+
+        /// <summary>
+        /// Tracks the recursive depth of the nested objects / arrays within the JSON text
+        /// processed so far. This provides the depth of the current token.
+        /// </summary>
+        public int CurrentDepth => _bitStack.CurrentDepth;
+
+        /// <summary>
+        /// Gets the type of the last processed JSON token in the UTF-8 encoded JSON text.
+        /// </summary>
+        public JsonTokenType TokenType => _tokenType;
+
+        /// <summary>
+        /// Lets the caller know which of the two 'Value' properties to read to get the 
+        /// token value. For input data within a ReadOnlySpan&lt;byte&gt; this will
+        /// always return false. For input data within a ReadOnlySequence&lt;byte&gt;, this
+        /// will only return true if the token value straddles more than a single segment and
+        /// hence couldn't be represented as a span.
+        /// </summary>
+        public bool HasValueSequence { get; private set; }
+
+        /// <summary>
+        /// Gets the value of the last processed token as a ReadOnlySpan&lt;byte&gt; slice
+        /// of the input payload. If the JSON is provided within a ReadOnlySequence&lt;byte&gt;
+        /// and the slice that represents the token value fits in a single segment, then
+        /// <see cref="ValueSpan"/> will contain the sliced value since it can be represented as a span.
+        /// Otherwise, the <see cref="ValueSequence"/> will contain the token value.
+        /// </summary>
+        /// <remarks>
+        /// If <see cref="HasValueSequence"/> is false, <see cref="ValueSequence"/> contains useless data, likely for
+        /// a previous multi-segment token. Therefore, only access <see cref="ValueSpan"/> if <see cref="HasValueSequence"/> is true.
+        /// Otherwise, the token value must be accessed from <see cref="ValueSpan"/>.
+        /// </remarks>
+        public ReadOnlySequence<byte> ValueSequence { get; private set; }
+
+        /// <summary>
+        /// Returns the current <see cref="SequencePosition"/> within the provided UTF-8 encoded
+        /// input ReadOnlySequence&lt;byte&gt;. If the <see cref="Utf8JsonReader"/> was constructed
+        /// with a ReadOnlySpan&lt;byte&gt; instead, this will always return a default <see cref="SequencePosition"/>.
+        /// </summary>
+        public SequencePosition Position
+        {
+            get
+            {
+                // TODO: Cannot use Slice even though it would be faster: https://github.com/dotnet/corefx/issues/33291
+                return _currentPosition.GetObject() == null
+                    ? default
+                    : _sequence.GetPosition(BytesConsumed);
+            }
+        }
+
+        /// <summary>
+        /// Returns the current snapshot of the <see cref="Utf8JsonReader"/> state which must
+        /// be captured by the caller and passed back in to the <see cref="Utf8JsonReader"/> ctor with more data.
+        /// Unlike the <see cref="Utf8JsonReader"/>, 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="Utf8JsonReader"/>.
+        /// </summary>
+        public JsonReaderState CurrentState => new JsonReaderState
+        {
+            _lineNumber = _lineNumber,
+            _bytePositionInLine = _bytePositionInLine,
+            _bytesConsumed = BytesConsumed,
+            _maxDepth = _maxDepth,
+            _inObject = _inObject,
+            _isNotPrimitive = _isNotPrimitive,
+            _numberFormat = _numberFormat,
+            _tokenType = _tokenType,
+            _previousTokenType = _previousTokenType,
+            _readerOptions = _readerOptions,
+            _bitStack = _bitStack,
+        };
+
+        /// <summary>
+        /// Constructs a new <see cref="Utf8JsonReader"/> instance.
+        /// </summary>
+        /// <param name="jsonData">The ReadOnlySpan&lt;byte&gt; containing the UTF-8 encoded JSON text to process.</param>
+        /// <param name="isFinalBlock">True when the input span contains the entire data to process.
+        /// Set to false only if it is known that the input span contains partial data with more data to follow.</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="Utf8JsonReader"/> and pass that back.</param>
+        /// <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="JsonReaderState"/>.
+        /// </remarks>
+        public Utf8JsonReader(ReadOnlySpan<byte> jsonData, bool isFinalBlock, JsonReaderState state)
+        {
+            _buffer = jsonData;
+
+            _isFinalBlock = isFinalBlock;
+
+            // Note: We do not retain _bytesConsumed or _sequencePosition as they reset with the new input data
+            _lineNumber = state._lineNumber;
+            _bytePositionInLine = state._bytePositionInLine;
+            _maxDepth = state._maxDepth == 0 ? JsonReaderState.DefaultMaxDepth : state._maxDepth; // If max depth is not set, revert to the default depth.
+            _inObject = state._inObject;
+            _isNotPrimitive = state._isNotPrimitive;
+            _numberFormat = state._numberFormat;
+            _tokenType = state._tokenType;
+            _previousTokenType = state._previousTokenType;
+            _readerOptions = state._readerOptions;
+            _bitStack = state._bitStack;
+
+            _consumed = 0;
+            _totalConsumed = 0;
+            _isLastSegment = _isFinalBlock;
+            _isSingleSegment = true;
+
+            ValueSpan = ReadOnlySpan<byte>.Empty;
+
+            _currentPosition = default;
+            _nextPosition = default;
+            _sequence = default;
+            HasValueSequence = false;
+            ValueSequence = ReadOnlySequence<byte>.Empty;
+        }
+
+        /// <summary>
+        /// Read the next JSON token from input source.
+        /// </summary>
+        /// <returns>True if the token was read successfully, else false.</returns>
+        /// <exception cref="JsonReaderException">
+        /// Thrown when an invalid JSON token is encountered according to the JSON RFC
+        /// or if the current depth exceeds the recursive limit set by the max depth.
+        /// </exception>
+        public bool Read()
+        {
+            return _isSingleSegment ? ReadSingleSegment() : ReadMultiSegment();
+        }
+
+        private void StartObject()
+        {
+            if (CurrentDepth >= _maxDepth)
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ObjectDepthTooLarge);
+
+            _bitStack.PushTrue();
+
+            _consumed++;
+            _bytePositionInLine++;
+            _tokenType = JsonTokenType.StartObject;
+            _inObject = true;
+        }
+
+        private void EndObject()
+        {
+            if (!_inObject || CurrentDepth <= 0)
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBrace);
+
+            _tokenType = JsonTokenType.EndObject;
+
+            UpdateBitStackOnEndToken();
+        }
+
+        private void StartArray()
+        {
+            if (CurrentDepth >= _maxDepth)
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ArrayDepthTooLarge);
+
+            _bitStack.PushFalse();
+
+            _consumed++;
+            _bytePositionInLine++;
+            _tokenType = JsonTokenType.StartArray;
+            _inObject = false;
+        }
+
+        private void EndArray()
+        {
+            if (_inObject || CurrentDepth <= 0)
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBracket);
+
+            _tokenType = JsonTokenType.EndArray;
+
+            UpdateBitStackOnEndToken();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void UpdateBitStackOnEndToken()
+        {
+            _consumed++;
+            _bytePositionInLine++;
+            _inObject = _bitStack.Pop();
+        }
+
+        private bool ReadSingleSegment()
+        {
+            bool retVal = false;
+
+            if (!HasMoreData())
+            {
+                goto Done;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            // SkipWhiteSpace only skips the whitespace characters as defined by JSON RFC 8259 section 2.
+            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
+            // Those cases are captured in ConsumeNextToken and ConsumeValue.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpace();
+                if (!HasMoreData())
+                {
+                    goto Done;
+                }
+                first = _buffer[_consumed];
+            }
+
+            if (_tokenType == JsonTokenType.None)
+            {
+                goto ReadFirstToken;
+            }
+
+            if (first == JsonConstants.Slash)
+            {
+                retVal = ConsumeNextTokenOrRollback(first);
+                goto Done;
+            }
+
+            if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (first == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    retVal = ConsumePropertyName();
+                    if (!retVal)
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (first == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    retVal = ConsumeValue(first);
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                retVal = ConsumeValue(first);
+                goto Done;
+            }
+            else
+            {
+                retVal = ConsumeNextTokenOrRollback(first);
+                goto Done;
+            }
+
+            retVal = true;
+
+        Done:
+            return retVal;
+
+        ReadFirstToken:
+            retVal = ReadFirstToken(first);
+            goto Done;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HasMoreData()
+        {
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (_isNotPrimitive && IsLastSpan)
+                {
+                    if (CurrentDepth != 0)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ZeroDepthAtEnd);
+                    }
+
+                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && _tokenType == JsonTokenType.Comment)
+                    {
+                        return false;
+                    }
+
+                    if (_tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
+                    }
+                }
+                return false;
+            }
+            return true;
+        }
+
+        // Unlike the parameter-less overload of HasMoreData, if there is no more data when this method is called, we know the JSON input is invalid.
+        // This is because, this method is only called after a ',' (i.e. we expect a value/property name) or after 
+        // a property name, which means it must be followed by a value.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool HasMoreData(ExceptionResource resource)
+        {
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, resource);
+                }
+                return false;
+            }
+            return true;
+        }
+
+        private bool ReadFirstToken(byte first)
+        {
+            if (first == JsonConstants.OpenBrace)
+            {
+                _bitStack.SetFirstBit();
+                _tokenType = JsonTokenType.StartObject;
+                _consumed++;
+                _bytePositionInLine++;
+                _inObject = true;
+                _isNotPrimitive = true;
+            }
+            else if (first == JsonConstants.OpenBracket)
+            {
+                _bitStack.ResetFirstBit();
+                _tokenType = JsonTokenType.StartArray;
+                _consumed++;
+                _bytePositionInLine++;
+                _isNotPrimitive = true;
+            }
+            else
+            {
+                // Create local copy to avoid bounds checks.
+                ReadOnlySpan<byte> localBuffer = _buffer;
+
+                if (JsonReaderHelper.IsDigit(first) || first == '-')
+                {
+                    if (!TryGetNumber(localBuffer.Slice(_consumed), out int numberOfBytes))
+                    {
+                        return false;
+                    }
+                    _tokenType = JsonTokenType.Number;
+                    _consumed += numberOfBytes;
+                    _bytePositionInLine += numberOfBytes;
+                }
+                else if (!ConsumeValue(first))
+                {
+                    return false;
+                }
+
+                // Cannot use HasMoreData since the JSON payload contains a single, non-primitive value
+                // and hence must be handled differently.
+                if (_consumed >= (uint)localBuffer.Length)
+                {
+                    goto SetIsNotPrimitiveAndReturnTrue;
+                }
+
+                if (localBuffer[_consumed] <= JsonConstants.Space)
+                {
+                    SkipWhiteSpace();
+                    if (_consumed >= (uint)localBuffer.Length)
+                    {
+                        goto SetIsNotPrimitiveAndReturnTrue;
+                    }
+                }
+
+                if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
+                {
+                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
+                    {
+                        // This is necessary to avoid throwing when the user has 1 or more comments as the first token
+                        // OR if there is a comment after a single, non-primitive value.
+                        // In this mode, ConsumeValue consumes the comment and we need to return it as a token.
+                        // along with future comments in subsequeunt reads.
+                        if (_tokenType == JsonTokenType.Comment || localBuffer[_consumed] == JsonConstants.Slash)
+                        {
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                        if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
+                        {
+                            _isNotPrimitive = true;
+                        }
+                        goto SetIsNotPrimitiveAndReturnTrue;
+                    }
+                }
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, localBuffer[_consumed]);
+
+            SetIsNotPrimitiveAndReturnTrue:
+                if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
+                {
+                    _isNotPrimitive = true;
+                }
+                // Intentionally fall out of the if-block to return true
+            }
+            return true;
+        }
+
+        private void SkipWhiteSpace()
+        {
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer;
+            for (; _consumed < localBuffer.Length; _consumed++)
+            {
+                byte val = localBuffer[_consumed];
+
+                // JSON RFC 8259 section 2 says only these 4 characters count, not all of the Unicode defintions of whitespace.
+                if (val != JsonConstants.Space &&
+                    val != JsonConstants.CarriageReturn &&
+                    val != JsonConstants.LineFeed &&
+                    val != JsonConstants.Tab)
+                {
+                    break;
+                }
+
+                if (val == JsonConstants.LineFeed)
+                {
+                    _lineNumber++;
+                    _bytePositionInLine = 0;
+                }
+                else
+                {
+                    _bytePositionInLine++;
+                }
+            }
+        }
+
+        /// <summary>
+        /// This method contains the logic for processing the next value token and determining
+        /// what type of data it is.
+        /// </summary>
+        private bool ConsumeValue(byte marker)
+        {
+            while (true)
+            {
+                if (marker == JsonConstants.Quote)
+                {
+                    return ConsumeString();
+                }
+                else if (marker == JsonConstants.OpenBrace)
+                {
+                    StartObject();
+                }
+                else if (marker == JsonConstants.OpenBracket)
+                {
+                    StartArray();
+                }
+                else if (JsonReaderHelper.IsDigit(marker) || marker == '-')
+                {
+                    return ConsumeNumber();
+                }
+                else if (marker == 'f')
+                {
+                    return ConsumeLiteral(JsonConstants.FalseValue, JsonTokenType.False);
+                }
+                else if (marker == 't')
+                {
+                    return ConsumeLiteral(JsonConstants.TrueValue, JsonTokenType.True);
+                }
+                else if (marker == 'n')
+                {
+                    return ConsumeLiteral(JsonConstants.NullValue, JsonTokenType.Null);
+                }
+                else
+                {
+                    switch (_readerOptions.CommentHandling)
+                    {
+                        case JsonCommentHandling.Disallow:
+                            break;
+                        case JsonCommentHandling.Allow:
+                            if (marker == JsonConstants.Slash)
+                            {
+                                return ConsumeComment();
+                            }
+                            break;
+                        default:
+                            Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                            if (marker == JsonConstants.Slash)
+                            {
+                                if (SkipComment())
+                                {
+                                    if (_consumed >= (uint)_buffer.Length)
+                                    {
+                                        if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
+                                        {
+                                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
+                                        }
+                                        return false;
+                                    }
+
+                                    marker = _buffer[_consumed];
+
+                                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                                    if (marker <= JsonConstants.Space)
+                                    {
+                                        SkipWhiteSpace();
+                                        if (!HasMoreData())
+                                        {
+                                            return false;
+                                        }
+                                        marker = _buffer[_consumed];
+                                    }
+
+                                    // Skip comments and consume the actual JSON value.
+                                    continue;
+                                }
+                                return false;
+                            }
+                            break;
+                    }
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+                }
+                break;
+            }
+            return true;
+        }
+
+        // Consumes 'null', or 'true', or 'false'
+        private bool ConsumeLiteral(ReadOnlySpan<byte> literal, JsonTokenType tokenType)
+        {
+            ReadOnlySpan<byte> span = _buffer.Slice(_consumed);
+            Debug.Assert(span.Length > 0);
+            Debug.Assert(span[0] == 'n' || span[0] == 't' || span[0] == 'f');
+
+            if (!span.StartsWith(literal))
+            {
+                return CheckLiteral(span, literal);
+            }
+
+            ValueSpan = span.Slice(0, literal.Length);
+            _tokenType = tokenType;
+            _consumed += literal.Length;
+            _bytePositionInLine += literal.Length;
+            return true;
+        }
+
+        private bool CheckLiteral(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal)
+        {
+            Debug.Assert(span.Length > 0 && span[0] == literal[0]);
+
+            int indexOfFirstMismatch = 0;
+
+            for (int i = 1; i < literal.Length; i++)
+            {
+                if (span.Length > i)
+                {
+                    if (span[i] != literal[i])
+                    {
+                        _bytePositionInLine += i;
+                        ThrowInvalidLiteral(span);
+                    }
+                }
+                else
+                {
+                    indexOfFirstMismatch = i;
+                    break;
+                }
+            }
+
+            Debug.Assert(indexOfFirstMismatch > 0 && indexOfFirstMismatch < literal.Length);
+
+            if (IsLastSpan)
+            {
+                _bytePositionInLine += indexOfFirstMismatch;
+                ThrowInvalidLiteral(span);
+            }
+            return false;
+        }
+
+        private void ThrowInvalidLiteral(ReadOnlySpan<byte> span)
+        {
+            byte firstByte = span[0];
+
+            ExceptionResource resource;
+            switch (firstByte)
+            {
+                case (byte)'t':
+                    resource = ExceptionResource.ExpectedTrue;
+                    break;
+                case (byte)'f':
+                    resource = ExceptionResource.ExpectedFalse;
+                    break;
+                default:
+                    Debug.Assert(firstByte == 'n');
+                    resource = ExceptionResource.ExpectedNull;
+                    break;
+            }
+            ThrowHelper.ThrowJsonReaderException(ref this, resource, bytes: span);
+        }
+
+        private bool ConsumeNumber()
+        {
+            if (!TryGetNumber(_buffer.Slice(_consumed), out int consumed))
+            {
+                return false;
+            }
+
+            _tokenType = JsonTokenType.Number;
+            _consumed += consumed;
+            _bytePositionInLine += consumed;
+
+            if (_consumed >= (uint)_buffer.Length)
+            {
+                if (!_isNotPrimitive)
+                {
+                    return true;
+                }
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed - 1]);
+                }
+                return false;
+            }
+
+            // TODO: https://github.com/dotnet/corefx/issues/33294
+            if (JsonConstants.Delimiters.IndexOf(_buffer[_consumed]) < 0)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed]);
+            }
+            return true;
+        }
+
+        private bool ConsumePropertyName()
+        {
+            if (!ConsumeString())
+            {
+                return false;
+            }
+
+            if (!HasMoreData(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
+            {
+                return false;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
+            // Those cases are captured below where we only accept ':'.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpace();
+                if (!HasMoreData(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
+                {
+                    return false;
+                }
+                first = _buffer[_consumed];
+            }
+
+            // The next character must be a key / value seperator. Validate and skip.
+            if (first != JsonConstants.KeyValueSeperator)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedSeparatorAfterPropertyNameNotFound, first);
+            }
+
+            _consumed++;
+            _bytePositionInLine++;
+            _tokenType = JsonTokenType.PropertyName;
+            return true;
+        }
+
+        private bool ConsumeString()
+        {
+            Debug.Assert(_buffer.Length >= _consumed + 1);
+            Debug.Assert(_buffer[_consumed] == JsonConstants.Quote);
+
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+
+            // Vectorized search for either quote, backslash, or any control character.
+            // If the first found byte is a quote, we have reached an end of string, and
+            // can avoid validation.
+            // Otherwise, in the uncommon case, iterate one character at a time and validate.
+            int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
+
+            if (idx >= 0)
+            {
+                byte foundByte = localBuffer[idx];
+                if (foundByte == JsonConstants.Quote)
+                {
+                    _bytePositionInLine += idx + 2; // Add 2 for the start and end quotes.
+                    ValueSpan = localBuffer.Slice(0, idx);
+                    _tokenType = JsonTokenType.String;
+                    _consumed += idx + 2;
+                    return true;
+                }
+                else
+                {
+                    return ConsumeStringAndValidate(localBuffer, idx);
+                }
+            }
+            else
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                }
+                return false;
+            }
+        }
+
+        // Found a backslash or control characters which are considered invalid within a string.
+        // Search through the rest of the string one byte at a time.
+        // https://tools.ietf.org/html/rfc8259#section-7
+        private bool ConsumeStringAndValidate(ReadOnlySpan<byte> data, int idx)
+        {
+            Debug.Assert(idx >= 0 && idx < data.Length);
+            Debug.Assert(data[idx] != JsonConstants.Quote);
+            Debug.Assert(data[idx] == JsonConstants.BackSlash || data[idx] < JsonConstants.Space);
+
+            long prevLineBytePosition = _bytePositionInLine;
+            long prevLineNumber = _lineNumber;
+
+            _bytePositionInLine += idx + 1; // Add 1 for the first quote
+
+            bool nextCharEscaped = false;
+            for (; idx < data.Length; idx++)
+            {
+                byte currentByte = data[idx];
+                if (currentByte == JsonConstants.Quote)
+                {
+                    if (!nextCharEscaped)
+                    {
+                        goto Done;
+                    }
+                    nextCharEscaped = false;
+                }
+                else if (currentByte == JsonConstants.BackSlash)
+                {
+                    nextCharEscaped = !nextCharEscaped;
+                }
+                else if (nextCharEscaped)
+                {
+                    int index = JsonConstants.EscapableChars.IndexOf(currentByte);
+                    if (index == -1)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
+                    }
+
+                    if (currentByte == JsonConstants.Quote)
+                    {
+                        // Ignore an escaped quote.
+                        // This is likely the most common case, so adding an explicit check
+                        // to avoid doing the unnecessary checks below.
+                    }
+                    else if (currentByte == 'n')
+                    {
+                        // Escaped new line character
+                        _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
+                        _lineNumber++;
+                    }
+                    else if (currentByte == 'u')
+                    {
+                        // Expecting 4 hex digits to follow the escaped 'u'
+                        _bytePositionInLine++;  // move past the 'u'
+                        if (ValidateHexDigits(data, idx + 1))
+                        {
+                            idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
+                        }
+                        else
+                        {
+                            // We found less than 4 hex digits. Check if there is more data to follow, otherwise throw.
+                            idx = data.Length;
+                            break;
+                        }
+
+                    }
+                    nextCharEscaped = false;
+                }
+                else if (currentByte < JsonConstants.Space)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
+                }
+
+                _bytePositionInLine++;
+            }
+
+            if (idx >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
+                }
+                _lineNumber = prevLineNumber;
+                _bytePositionInLine = prevLineBytePosition;
+                return false;
+            }
+
+        Done:
+            _bytePositionInLine++;  // Add 1 for the end quote
+            ValueSpan = data.Slice(0, idx);
+            _tokenType = JsonTokenType.String;
+            _consumed += idx + 2;
+            return true;
+        }
+
+        private bool ValidateHexDigits(ReadOnlySpan<byte> data, int idx)
+        {
+            for (int j = idx; j < data.Length; j++)
+            {
+                byte nextByte = data[j];
+                if (!JsonReaderHelper.IsHexDigit(nextByte))
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
+                }
+                if (j - idx >= 3)
+                {
+                    return true;
+                }
+                _bytePositionInLine++;
+            }
+
+            return false;
+        }
+
+        // https://tools.ietf.org/html/rfc7159#section-6
+        private bool TryGetNumber(ReadOnlySpan<byte> data, out int consumed)
+        {
+            // TODO: https://github.com/dotnet/corefx/issues/33294
+            Debug.Assert(data.Length > 0);
+
+            _numberFormat = default;
+            consumed = 0;
+            int i = 0;
+
+            ConsumeNumberResult signResult = ConsumeNegativeSign(ref data, ref i);
+            if (signResult == ConsumeNumberResult.NeedMoreData)
+            {
+                return false;
+            }
+
+            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
+
+            byte nextByte = data[i];
+            Debug.Assert(nextByte >= '0' && nextByte <= '9');
+
+            if (nextByte == '0')
+            {
+                ConsumeNumberResult result = ConsumeZero(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+            }
+            else
+            {
+                i++;
+                ConsumeNumberResult result = ConsumeIntegerDigits(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+                if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
+                {
+                    _bytePositionInLine += i;
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
+                }
+            }
+
+            Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');
+
+            if (nextByte == '.')
+            {
+                i++;
+                ConsumeNumberResult result = ConsumeDecimalDigits(ref data, ref i);
+                if (result == ConsumeNumberResult.NeedMoreData)
+                {
+                    return false;
+                }
+                if (result == ConsumeNumberResult.Success)
+                {
+                    goto Done;
+                }
+
+                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
+                nextByte = data[i];
+                if (nextByte != 'E' && nextByte != 'e')
+                {
+                    _bytePositionInLine += i;
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
+                }
+            }
+
+            Debug.Assert(nextByte == 'E' || nextByte == 'e');
+            i++;
+            _numberFormat = 'e';
+
+            signResult = ConsumeSign(ref data, ref i);
+            if (signResult == ConsumeNumberResult.NeedMoreData)
+            {
+                return false;
+            }
+
+            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
+
+            i++;
+            ConsumeNumberResult resultExponent = ConsumeIntegerDigits(ref data, ref i);
+            if (resultExponent == ConsumeNumberResult.NeedMoreData)
+            {
+                return false;
+            }
+            if (resultExponent == ConsumeNumberResult.Success)
+            {
+                goto Done;
+            }
+
+            Debug.Assert(resultExponent == ConsumeNumberResult.OperationIncomplete);
+
+            _bytePositionInLine += i;
+            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
+
+        Done:
+            ValueSpan = data.Slice(0, i);
+            consumed = i;
+            return true;
+        }
+
+        private ConsumeNumberResult ConsumeNegativeSign(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            byte nextByte = data[i];
+
+            if (nextByte == '-')
+            {
+                i++;
+                if (i >= data.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _bytePositionInLine += i;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+
+                nextByte = data[i];
+                if (!JsonReaderHelper.IsDigit(nextByte))
+                {
+                    _bytePositionInLine += i;
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
+                }
+            }
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeZero(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            Debug.Assert(data[i] == (byte)'0');
+            i++;
+            byte nextByte = default;
+            if (i < data.Length)
+            {
+                nextByte = data[i];
+                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
+                {
+                    return ConsumeNumberResult.Success;
+                }
+            }
+            else
+            {
+                if (IsLastSpan)
+                {
+                    // A payload containing a single value: "0" is valid
+                    // If we are v with multi-value JSON,
+                    // ConsumeNumber will validate that we have a delimiter following the "0".
+                    return ConsumeNumberResult.Success;
+                }
+                else
+                {
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+            }
+            nextByte = data[i];
+            if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
+            {
+                _bytePositionInLine += i;
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeIntegerDigits(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            byte nextByte = default;
+            for (; i < data.Length; i++)
+            {
+                nextByte = data[i];
+                if (!JsonReaderHelper.IsDigit(nextByte))
+                {
+                    break;
+                }
+            }
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    // A payload containing a single value of integers (e.g. "12") is valid
+                    // If we are dealing with multi-value JSON,
+                    // ConsumeNumber will validate that we have a delimiter following the integer.
+                    return ConsumeNumberResult.Success;
+                }
+                else
+                {
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+            }
+            if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
+            {
+                return ConsumeNumberResult.Success;
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private ConsumeNumberResult ConsumeDecimalDigits(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    _bytePositionInLine += i;
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                }
+                return ConsumeNumberResult.NeedMoreData;
+            }
+            byte nextByte = data[i];
+            if (!JsonReaderHelper.IsDigit(nextByte))
+            {
+                _bytePositionInLine += i;
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterDecimal, nextByte);
+            }
+            i++;
+
+            return ConsumeIntegerDigits(ref data, ref i);
+        }
+
+        private ConsumeNumberResult ConsumeSign(ref ReadOnlySpan<byte> data, ref int i)
+        {
+            if (i >= data.Length)
+            {
+                if (IsLastSpan)
+                {
+                    _bytePositionInLine += i;
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                }
+                return ConsumeNumberResult.NeedMoreData;
+            }
+
+            byte nextByte = data[i];
+            if (nextByte == '+' || nextByte == '-')
+            {
+                i++;
+                if (i >= data.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _bytePositionInLine += i;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
+                    }
+                    return ConsumeNumberResult.NeedMoreData;
+                }
+                nextByte = data[i];
+            }
+
+            if (!JsonReaderHelper.IsDigit(nextByte))
+            {
+                _bytePositionInLine += i;
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
+            }
+
+            return ConsumeNumberResult.OperationIncomplete;
+        }
+
+        private bool ConsumeNextTokenOrRollback(byte marker)
+        {
+            int prevConsumed = _consumed;
+            long prevPosition = _bytePositionInLine;
+            long prevLineNumber = _lineNumber;
+            JsonTokenType prevTokenType = _tokenType;
+            ConsumeTokenResult result = ConsumeNextToken(marker);
+            if (result == ConsumeTokenResult.Success)
+            {
+                return true;
+            }
+            if (result == ConsumeTokenResult.NotEnoughDataRollBackState)
+            {
+                _consumed = prevConsumed;
+                _tokenType = prevTokenType;
+                _bytePositionInLine = prevPosition;
+                _lineNumber = prevLineNumber;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// This method consumes the next token regardless of whether we are inside an object or an array.
+        /// For an object, it reads the next property name token. For an array, it just reads the next value.
+        /// </summary>
+        private ConsumeTokenResult ConsumeNextToken(byte marker)
+        {
+            if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
+            {
+                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
+                {
+                    if (marker == JsonConstants.Slash)
+                    {
+                        return ConsumeComment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    if (_tokenType == JsonTokenType.Comment)
+                    {
+                        return ConsumeNextTokenFromLastNonCommentToken();
+                    }
+                }
+                else
+                {
+                    Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
+                    return ConsumeNextTokenUntilAfterAllCommentsAreSkipped(marker);
+                }
+            }
+
+            if (!_isNotPrimitive)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
+            }
+
+            if (marker == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    return ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                byte first = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (first <= JsonConstants.Space)
+                {
+                    SkipWhiteSpace();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    first = _buffer[_consumed];
+                }
+
+                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash)
+                {
+                    return ConsumeComment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+
+                if (_inObject)
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+                    return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                else
+                {
+                    return ConsumeValue(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+            }
+            else if (marker == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (marker == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
+            }
+            return ConsumeTokenResult.Success;
+        }
+
+        private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken()
+        {
+            if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType))
+            {
+                _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray;
+            }
+            else
+            {
+                _tokenType = _previousTokenType;
+            }
+
+            Debug.Assert(_tokenType != JsonTokenType.Comment);
+
+            if (!HasMoreData())
+            {
+                goto RollBack;
+            }
+
+            byte first = _buffer[_consumed];
+
+            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+            if (first <= JsonConstants.Space)
+            {
+                SkipWhiteSpace();
+                if (!HasMoreData())
+                {
+                    goto RollBack;
+                }
+                first = _buffer[_consumed];
+            }
+
+            if (!_isNotPrimitive && _tokenType != JsonTokenType.None)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, first);
+            }
+
+            Debug.Assert(first != JsonConstants.Slash);
+
+            if (first == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    goto RollBack;
+                }
+                first = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (first <= JsonConstants.Space)
+                {
+                    SkipWhiteSpace();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        goto RollBack;
+                    }
+                    first = _buffer[_consumed];
+                }
+
+                if (_inObject)
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+                    if (ConsumePropertyName())
+                    {
+                        goto Done;
+                    }
+                    else
+                    {
+                        goto RollBack;
+                    }
+                }
+                else
+                {
+                    if (ConsumeValue(first))
+                    {
+                        goto Done;
+                    }
+                    else
+                    {
+                        goto RollBack;
+                    }
+                }
+            }
+            else if (first == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (first == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else if (_tokenType == JsonTokenType.None)
+            {
+                if (ReadFirstToken(first))
+                {
+                    goto Done;
+                }
+                else
+                {
+                    goto RollBack;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (first == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (first != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
+                    }
+
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    if (!ConsumePropertyName())
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                        goto RollBack;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (first == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    if (!ConsumeValue(first))
+                    {
+                        goto RollBack;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                if (!ConsumeValue(first))
+                {
+                    goto RollBack;
+                }
+                goto Done;
+            }
+            else
+            {
+                goto RollBack;
+            }
+
+        Done:
+            return ConsumeTokenResult.Success;
+
+        RollBack:
+            return ConsumeTokenResult.NotEnoughDataRollBackState;
+        }
+
+        private bool SkipAllComments(ref byte marker)
+        {
+            while (marker == JsonConstants.Slash)
+            {
+                if (SkipComment())
+                {
+                    if (!HasMoreData())
+                    {
+                        goto IncompleteNoRollback;
+                    }
+
+                    marker = _buffer[_consumed];
+
+                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                    if (marker <= JsonConstants.Space)
+                    {
+                        SkipWhiteSpace();
+                        if (!HasMoreData())
+                        {
+                            goto IncompleteNoRollback;
+                        }
+                        marker = _buffer[_consumed];
+                    }
+                }
+                else
+                {
+                    goto IncompleteNoRollback;
+                }
+            }
+            return true;
+
+        IncompleteNoRollback:
+            return false;
+        }
+
+        private bool SkipAllComments(ref byte marker, ExceptionResource resource)
+        {
+            while (marker == JsonConstants.Slash)
+            {
+                if (SkipComment())
+                {
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreData(resource))
+                    {
+                        goto IncompleteRollback;
+                    }
+
+                    marker = _buffer[_consumed];
+
+                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                    if (marker <= JsonConstants.Space)
+                    {
+                        SkipWhiteSpace();
+                        // The next character must be a start of a property name or value.
+                        if (!HasMoreData(resource))
+                        {
+                            goto IncompleteRollback;
+                        }
+                        marker = _buffer[_consumed];
+                    }
+                }
+                else
+                {
+                    goto IncompleteRollback;
+                }
+            }
+            return true;
+
+        IncompleteRollback:
+            return false;
+        }
+
+        private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkipped(byte marker)
+        {
+            if (!SkipAllComments(ref marker))
+            {
+                goto IncompleteNoRollback;
+            }
+
+            if (_tokenType == JsonTokenType.StartObject)
+            {
+                if (marker == JsonConstants.CloseBrace)
+                {
+                    EndObject();
+                }
+                else
+                {
+                    if (marker != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
+                    }
+
+                    int prevConsumed = _consumed;
+                    long prevPosition = _bytePositionInLine;
+                    long prevLineNumber = _lineNumber;
+                    if (!ConsumePropertyName())
+                    {
+                        // roll back potential changes
+                        _consumed = prevConsumed;
+                        _tokenType = JsonTokenType.StartObject;
+                        _bytePositionInLine = prevPosition;
+                        _lineNumber = prevLineNumber;
+                        goto IncompleteNoRollback;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.StartArray)
+            {
+                if (marker == JsonConstants.CloseBracket)
+                {
+                    EndArray();
+                }
+                else
+                {
+                    if (!ConsumeValue(marker))
+                    {
+                        goto IncompleteNoRollback;
+                    }
+                    goto Done;
+                }
+            }
+            else if (_tokenType == JsonTokenType.PropertyName)
+            {
+                if (!ConsumeValue(marker))
+                {
+                    goto IncompleteNoRollback;
+                }
+                goto Done;
+            }
+            else if (!_isNotPrimitive)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
+            }
+            else if (marker == JsonConstants.ListSeparator)
+            {
+                _consumed++;
+                _bytePositionInLine++;
+
+                if (_consumed >= (uint)_buffer.Length)
+                {
+                    if (IsLastSpan)
+                    {
+                        _consumed--;
+                        _bytePositionInLine--;
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
+                    }
+                    return ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                marker = _buffer[_consumed];
+
+                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
+                if (marker <= JsonConstants.Space)
+                {
+                    SkipWhiteSpace();
+                    // The next character must be a start of a property name or value.
+                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                    {
+                        return ConsumeTokenResult.NotEnoughDataRollBackState;
+                    }
+                    marker = _buffer[_consumed];
+                }
+
+                if (!SkipAllComments(ref marker, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
+                {
+                    goto IncompleteRollback;
+                }
+
+                if (_inObject)
+                {
+                    if (marker != JsonConstants.Quote)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
+                    }
+                    return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+                else
+                {
+                    return ConsumeValue(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
+                }
+            }
+            else if (marker == JsonConstants.CloseBrace)
+            {
+                EndObject();
+            }
+            else if (marker == JsonConstants.CloseBracket)
+            {
+                EndArray();
+            }
+            else
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
+            }
+
+        Done:
+            return ConsumeTokenResult.Success;
+        IncompleteNoRollback:
+            return ConsumeTokenResult.IncompleteNoRollBackNecessary;
+        IncompleteRollback:
+            return ConsumeTokenResult.NotEnoughDataRollBackState;
+        }
+
+        private bool SkipComment()
+        {
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+
+            if (localBuffer.Length > 0)
+            {
+                byte marker = localBuffer[0];
+                if (marker == JsonConstants.Slash)
+                {
+                    return SkipSingleLineComment(localBuffer.Slice(1), out _);
+                }
+                else if (marker == JsonConstants.Asterisk)
+                {
+                    return SkipMultiLineComment(localBuffer.Slice(1), out _);
+                }
+                else
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+                }
+            }
+
+            if (IsLastSpan)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+            }
+            return false;
+        }
+
+        private bool SkipSingleLineComment(ReadOnlySpan<byte> localBuffer, out int idx)
+        {
+            // TODO: https://github.com/dotnet/corefx/issues/33293
+            idx = localBuffer.IndexOf(JsonConstants.LineFeed);
+            if (idx == -1)
+            {
+                if (IsLastSpan)
+                {
+                    idx = localBuffer.Length;
+                    // Assume everything on this line is a comment and there is no more data.
+                    _bytePositionInLine += 2 + localBuffer.Length;
+                    goto Done;
+                }
+                return false;
+            }
+
+            idx++;
+            _bytePositionInLine = 0;
+            _lineNumber++;
+        Done:
+            _consumed += 2 + idx;
+            return true;
+        }
+
+        private bool SkipMultiLineComment(ReadOnlySpan<byte> localBuffer, out int idx)
+        {
+            idx = 0;
+            while (true)
+            {
+                int foundIdx = localBuffer.Slice(idx).IndexOf(JsonConstants.Slash);
+                if (foundIdx == -1)
+                {
+                    if (IsLastSpan)
+                    {
+                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfCommentNotFound);
+                    }
+                    return false;
+                }
+                if (foundIdx != 0 && localBuffer[foundIdx + idx - 1] == JsonConstants.Asterisk)
+                {
+                    idx += foundIdx;
+                    break;
+                }
+                idx += foundIdx + 1;
+            }
+
+            Debug.Assert(idx >= 1);
+
+            // Consume the /* and */ characters that are part of the multi-line comment.
+            // Since idx is pointing at right after the final '*' (i.e. before the last '/'), we don't need to count that character.
+            // Hence, we increment consumed by 3 (instead of 4).
+            _consumed += 4 + idx - 1;
+
+            (int newLines, int newLineIndex) = JsonReaderHelper.CountNewLines(localBuffer.Slice(0, idx - 1));
+            _lineNumber += newLines;
+            if (newLineIndex != -1)
+            {
+                _bytePositionInLine = idx - newLineIndex;
+            }
+            else
+            {
+                _bytePositionInLine += 4 + idx - 1;
+            }
+            return true;
+        }
+
+        private bool ConsumeComment()
+        {
+            // Create local copy to avoid bounds checks.
+            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
+
+            if (localBuffer.Length > 0)
+            {
+                byte marker = localBuffer[0];
+                if (marker == JsonConstants.Slash)
+                {
+                    return ConsumeSingleLineComment(localBuffer.Slice(1), _consumed);
+                }
+                else if (marker == JsonConstants.Asterisk)
+                {
+                    return ConsumeMultiLineComment(localBuffer.Slice(1), _consumed);
+                }
+                else
+                {
+                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
+                }
+            }
+
+            if (IsLastSpan)
+            {
+                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
+            }
+            return false;
+        }
+
+        private bool ConsumeSingleLineComment(ReadOnlySpan<byte> localBuffer, int previousConsumed)
+        {
+            if (!SkipSingleLineComment(localBuffer, out int idx))
+            {
+                return false;
+            }
+
+            ValueSpan = _buffer.Slice(previousConsumed, idx + 2);   // Include the double slash and potential line feed at the end of the comment as part of it.
+            if (_tokenType != JsonTokenType.Comment)
+            {
+                _previousTokenType = _tokenType;
+            }
+            _tokenType = JsonTokenType.Comment;
+            return true;
+        }
+
+        private bool ConsumeMultiLineComment(ReadOnlySpan<byte> localBuffer, int previousConsumed)
+        {
+            if (!SkipMultiLineComment(localBuffer, out int idx))
+            {
+                return false;
+            }
+
+            ValueSpan = _buffer.Slice(previousConsumed, idx + 3); // Include the slash/asterisk and final slash at the end of the comment as part of it.
+            if (_tokenType != JsonTokenType.Comment)
+            {
+                _previousTokenType = _tokenType;
+            }
+            _tokenType = JsonTokenType.Comment;
+            return true;
+        }
+    }
+}
index 14e492732b23ef6498954f28840228f3400d9424..4831b531541eb94e3c37c8b74edfb7c85775a724 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/Utf8JsonReader.MultiSegment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.MultiSegment.cs
deleted file mode 100644 (file)
index 58eea7f..0000000
+++ /dev/null
@@ -1,2432 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace System.Text.Json
-{
-    public ref partial struct Utf8JsonReader
-    {
-        /// <summary>
-        /// Constructs a new <see cref="Utf8JsonReader"/> instance.
-        /// </summary>
-        /// <param name="jsonData">The ReadOnlySequence&lt;byte&gt; containing the UTF-8 encoded JSON text to process.</param>
-        /// <param name="isFinalBlock">True when the input span contains the entire data to process.
-        /// Set to false only if it is known that the input span contains partial data with more data to follow.</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="Utf8JsonReader"/> and pass that back.</param>
-        /// <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="JsonReaderState"/>.
-        /// </remarks>
-        public Utf8JsonReader(in ReadOnlySequence<byte> jsonData, bool isFinalBlock, JsonReaderState state)
-        {
-            _buffer = jsonData.First.Span;
-
-            _isFinalBlock = isFinalBlock;
-
-            // Note: We do not retain _bytesConsumed or _sequencePosition as they reset with the new input data
-            _lineNumber = state._lineNumber;
-            _bytePositionInLine = state._bytePositionInLine;
-            _maxDepth = state._maxDepth == 0 ? JsonReaderState.DefaultMaxDepth : state._maxDepth; // If max depth is not set, revert to the default depth.
-            _inObject = state._inObject;
-            _isNotPrimitive = state._isNotPrimitive;
-            _numberFormat = state._numberFormat;
-            _tokenType = state._tokenType;
-            _previousTokenType = state._previousTokenType;
-            _readerOptions = state._readerOptions;
-            _bitStack = state._bitStack;
-
-            _consumed = 0;
-            _totalConsumed = 0;
-
-            ValueSpan = ReadOnlySpan<byte>.Empty;
-
-            _sequence = jsonData;
-            HasValueSequence = false;
-            ValueSequence = ReadOnlySequence<byte>.Empty;
-
-            if (jsonData.IsSingleSegment)
-            {
-                _nextPosition = default;
-                _currentPosition = default;
-                _isLastSegment = isFinalBlock;
-                _isSingleSegment = true;
-            }
-            else
-            {
-                _nextPosition = jsonData.Start;
-                if (_buffer.Length == 0)
-                {
-                    while (jsonData.TryGet(ref _nextPosition, out ReadOnlyMemory<byte> memory, advance: true))
-                    {
-                        if (memory.Length != 0)
-                        {
-                            _buffer = memory.Span;
-                            break;
-                        }
-                    }
-                }
-
-                _currentPosition = _nextPosition;
-                _isLastSegment = !jsonData.TryGet(ref _nextPosition, out _, advance: true) && isFinalBlock; // Don't re-order to avoid short-circuiting
-                _isSingleSegment = false;
-            }
-        }
-
-        private bool ReadMultiSegment()
-        {
-            bool retVal = false;
-            HasValueSequence = false;
-
-            if (!HasMoreDataMultiSegment())
-            {
-                goto Done;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            // SkipWhiteSpace only skips the whitespace characters as defined by JSON RFC 8259 section 2.
-            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
-            // Those cases are captured in ConsumeNextToken and ConsumeValue.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpaceMultiSegment();
-                if (!HasMoreDataMultiSegment())
-                {
-                    goto Done;
-                }
-                first = _buffer[_consumed];
-            }
-
-            if (_tokenType == JsonTokenType.None)
-            {
-                goto ReadFirstToken;
-            }
-
-            if (first == JsonConstants.Slash)
-            {
-                retVal = ConsumeNextTokenOrRollbackMultiSegment(first);
-                goto Done;
-            }
-
-            if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (first == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-
-                    long prevTotalConsumed = _totalConsumed;
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    retVal = ConsumePropertyNameMultiSegment();
-                    if (!retVal)
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                        _totalConsumed = prevTotalConsumed;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (first == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    retVal = ConsumeValueMultiSegment(first);
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                retVal = ConsumeValueMultiSegment(first);
-                goto Done;
-            }
-            else
-            {
-                retVal = ConsumeNextTokenOrRollbackMultiSegment(first);
-                goto Done;
-            }
-
-            retVal = true;
-
-        Done:
-            return retVal;
-
-        ReadFirstToken:
-            retVal = ReadFirstTokenMultiSegment(first);
-            goto Done;
-        }
-
-        private bool ValidateStateAtEndOfData()
-        {
-            Debug.Assert(_isNotPrimitive && IsLastSpan);
-
-            if (_bitStack.CurrentDepth != 0)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ZeroDepthAtEnd);
-            }
-
-            if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && _tokenType == JsonTokenType.Comment)
-            {
-                return false;
-            }
-
-            if (_tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
-            }
-
-            return true;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private bool HasMoreDataMultiSegment()
-        {
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (_isNotPrimitive && IsLastSpan)
-                {
-                    if (!ValidateStateAtEndOfData())
-                    {
-                        return false;
-                    }
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (_isNotPrimitive && IsLastSpan)
-                    {
-                        ValidateStateAtEndOfData();
-                    }
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        // Unlike the parameter-less overload of HasMoreData, if there is no more data when this method is called, we know the JSON input is invalid.
-        // This is because, this method is only called after a ',' (i.e. we expect a value/property name) or after 
-        // a property name, which means it must be followed by a value.
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private bool HasMoreDataMultiSegment(ExceptionResource resource)
-        {
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, resource);
-                }
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, resource);
-                    }
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private bool GetNextSpan()
-        {
-            ReadOnlyMemory<byte> memory = default;
-            while (true)
-            {
-                SequencePosition copy = _currentPosition;
-                _currentPosition = _nextPosition;
-                bool noMoreData = !_sequence.TryGet(ref _nextPosition, out memory, advance: true);
-                if (noMoreData)
-                {
-                    _currentPosition = copy;
-                    _isLastSegment = true;
-                    return false;
-                }
-                if (memory.Length != 0)
-                {
-                    break;
-                }
-            }
-
-            if (_isFinalBlock)
-            {
-                _isLastSegment = !_sequence.TryGet(ref _nextPosition, out _, advance: false);
-            }
-
-            _buffer = memory.Span;
-            _totalConsumed += _consumed;
-            _consumed = 0;
-
-            return true;
-        }
-
-        private bool ReadFirstTokenMultiSegment(byte first)
-        {
-            if (first == JsonConstants.OpenBrace)
-            {
-                _bitStack.SetFirstBit();
-                _tokenType = JsonTokenType.StartObject;
-                _consumed++;
-                _bytePositionInLine++;
-                _inObject = true;
-                _isNotPrimitive = true;
-            }
-            else if (first == JsonConstants.OpenBracket)
-            {
-                _bitStack.ResetFirstBit();
-                _tokenType = JsonTokenType.StartArray;
-                _consumed++;
-                _bytePositionInLine++;
-                _isNotPrimitive = true;
-            }
-            else
-            {
-                if (JsonReaderHelper.IsDigit(first) || first == '-')
-                {
-                    if (!TryGetNumberMultiSegment(_buffer.Slice(_consumed), out int numberOfBytes))
-                    {
-                        return false;
-                    }
-                    _tokenType = JsonTokenType.Number;
-                    _consumed += numberOfBytes;
-                }
-                else if (!ConsumeValueMultiSegment(first))
-                {
-                    return false;
-                }
-
-                // Cannot use HasMoreData since the JSON payload contains a single, non-primitive value
-                // and hence must be handled differently.
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    goto SetIsNotPrimitiveAndReturnTrue;
-                }
-
-                if (_buffer[_consumed] <= JsonConstants.Space)
-                {
-                    SkipWhiteSpaceMultiSegment();
-                    if (_consumed >= (uint)_buffer.Length)
-                    {
-                        goto SetIsNotPrimitiveAndReturnTrue;
-                    }
-                }
-
-                if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
-                {
-                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
-                    {
-                        // This is necessary to avoid throwing when the user has 1 or more comments as the first token
-                        // OR if there is a comment after a single, non-primitive value.
-                        // In this mode, ConsumeValue consumes the comment and we need to return it as a token.
-                        // along with future comments in subsequeunt reads.
-                        if (_tokenType == JsonTokenType.Comment || _buffer[_consumed] == JsonConstants.Slash)
-                        {
-                            return true;
-                        }
-                    }
-                    else
-                    {
-                        Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                        goto SetIsNotPrimitiveAndReturnTrue;
-                    }
-                }
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, _buffer[_consumed]);
-
-            SetIsNotPrimitiveAndReturnTrue:
-                if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
-                {
-                    _isNotPrimitive = true;
-                }
-                // Intentionally fall out of the if-block to return true
-            }
-            return true;
-        }
-
-        private void SkipWhiteSpaceMultiSegment()
-        {
-            while (true)
-            {
-                SkipWhiteSpace();
-
-                if (_consumed < _buffer.Length)
-                {
-                    break;
-                }
-
-                if (!GetNextSpan())
-                {
-                    break;
-                }
-            }
-        }
-
-        /// <summary>
-        /// This method contains the logic for processing the next value token and determining
-        /// what type of data it is.
-        /// </summary>
-        private bool ConsumeValueMultiSegment(byte marker)
-        {
-            while (true)
-            {
-                if (marker == JsonConstants.Quote)
-                {
-                    return ConsumeStringMultiSegment();
-                }
-                else if (marker == JsonConstants.OpenBrace)
-                {
-                    StartObject();
-                }
-                else if (marker == JsonConstants.OpenBracket)
-                {
-                    StartArray();
-                }
-                else if (JsonReaderHelper.IsDigit(marker) || marker == '-')
-                {
-                    return ConsumeNumberMultiSegment();
-                }
-                else if (marker == 'f')
-                {
-                    return ConsumeLiteralMultiSegment(JsonConstants.FalseValue, JsonTokenType.False);
-                }
-                else if (marker == 't')
-                {
-                    return ConsumeLiteralMultiSegment(JsonConstants.TrueValue, JsonTokenType.True);
-                }
-                else if (marker == 'n')
-                {
-                    return ConsumeLiteralMultiSegment(JsonConstants.NullValue, JsonTokenType.Null);
-                }
-                else
-                {
-                    switch (_readerOptions.CommentHandling)
-                    {
-                        case JsonCommentHandling.Disallow:
-                            break;
-                        case JsonCommentHandling.Allow:
-                            if (marker == JsonConstants.Slash)
-                            {
-                                return ConsumeCommentMultiSegment();
-                            }
-                            break;
-                        default:
-                            Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                            if (marker == JsonConstants.Slash)
-                            {
-                                if (SkipCommentMultiSegment())
-                                {
-                                    if (_consumed >= (uint)_buffer.Length)
-                                    {
-                                        if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
-                                        {
-                                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
-                                        }
-                                        if (!GetNextSpan())
-                                        {
-                                            if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
-                                            {
-                                                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
-                                            }
-                                            return false;
-                                        }
-                                    }
-
-                                    marker = _buffer[_consumed];
-
-                                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                                    if (marker <= JsonConstants.Space)
-                                    {
-                                        SkipWhiteSpaceMultiSegment();
-                                        if (!HasMoreDataMultiSegment())
-                                        {
-                                            return false;
-                                        }
-                                        marker = _buffer[_consumed];
-                                    }
-
-                                    // Skip comments and consume the actual JSON value.
-                                    continue;
-                                }
-                                return false;
-                            }
-                            break;
-                    }
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-                }
-                break;
-            }
-            return true;
-        }
-
-        // Consumes 'null', or 'true', or 'false'
-        private bool ConsumeLiteralMultiSegment(ReadOnlySpan<byte> literal, JsonTokenType tokenType)
-        {
-            ReadOnlySpan<byte> span = _buffer.Slice(_consumed);
-            Debug.Assert(span.Length > 0);
-            Debug.Assert(span[0] == 'n' || span[0] == 't' || span[0] == 'f');
-
-            int consumed = literal.Length;
-
-            if (!span.StartsWith(literal))
-            {
-                int prevConsumed = _consumed;
-                if (CheckLiteralMultiSegment(span, literal, out consumed))
-                {
-                    goto Done;
-                }
-                _consumed = prevConsumed;
-                return false;
-            }
-
-            ValueSpan = span.Slice(0, literal.Length);
-            HasValueSequence = false;
-        Done:
-            _tokenType = tokenType;
-            _consumed += consumed;
-            _bytePositionInLine += consumed;
-            return true;
-        }
-
-        private bool CheckLiteralMultiSegment(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal, out int consumed)
-        {
-            Debug.Assert(span.Length > 0 && span[0] == literal[0]);
-
-            Span<byte> readSoFar = stackalloc byte[literal.Length];
-            int written = 0;
-
-            long prevTotalConsumed = _totalConsumed;
-            if (span.Length >= literal.Length || IsLastSpan)
-            {
-                _bytePositionInLine += FindMismatch(span, literal);
-
-                int amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
-                span.Slice(0, amountToWrite).CopyTo(readSoFar);
-                written += amountToWrite;
-                goto Throw;
-            }
-            else
-            {
-                if (!literal.StartsWith(span))
-                {
-                    _bytePositionInLine += FindMismatch(span, literal);
-                    int amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
-                    span.Slice(0, amountToWrite).CopyTo(readSoFar);
-                    written += amountToWrite;
-                    goto Throw;
-                }
-
-                ReadOnlySpan<byte> leftToMatch = literal.Slice(span.Length);
-
-                SequencePosition startPosition = _currentPosition;
-                int startConsumed = _consumed;
-                int alreadyMatched = literal.Length - leftToMatch.Length;
-                while (true)
-                {
-                    _totalConsumed += alreadyMatched;
-                    _bytePositionInLine += alreadyMatched;
-                    if (!GetNextSpan())
-                    {
-                        _totalConsumed = prevTotalConsumed;
-                        consumed = default;
-                        if (IsLastSpan)
-                        {
-                            goto Throw;
-                        }
-                        return false;
-                    }
-
-                    int amountToWrite = Math.Min(span.Length, readSoFar.Length - written);
-                    span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written));
-                    written += amountToWrite;
-
-                    span = _buffer;
-
-                    if (span.StartsWith(leftToMatch))
-                    {
-                        HasValueSequence = true;
-                        SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
-                        SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + leftToMatch.Length);
-                        ValueSequence = _sequence.Slice(start, end);
-                        consumed = leftToMatch.Length;
-                        return true;
-                    }
-
-                    if (!leftToMatch.StartsWith(span))
-                    {
-                        _bytePositionInLine += FindMismatch(span, leftToMatch);
-
-                        amountToWrite = Math.Min(span.Length, (int)_bytePositionInLine + 1);
-                        span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written));
-                        written += amountToWrite;
-
-                        goto Throw;
-                    }
-
-                    leftToMatch = leftToMatch.Slice(span.Length);
-                    alreadyMatched = span.Length;
-                }
-            }
-        Throw:
-            _totalConsumed = prevTotalConsumed;
-            consumed = default;
-            throw GetInvalidLiteralMultiSegment(readSoFar.Slice(0, written).ToArray());
-        }
-
-        private int FindMismatch(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal)
-        {
-            Debug.Assert(span.Length > 0);
-
-            int indexOfFirstMismatch = 0;
-
-            int minLength = Math.Min(span.Length, literal.Length);
-
-            int i = 0;
-            for (; i < minLength; i++)
-            {
-                if (span[i] != literal[i])
-                {
-                    break;
-                }
-            }
-            indexOfFirstMismatch = i;
-
-            Debug.Assert(indexOfFirstMismatch >= 0 && indexOfFirstMismatch < literal.Length);
-
-            return indexOfFirstMismatch;
-        }
-
-        private JsonReaderException GetInvalidLiteralMultiSegment(ReadOnlySpan<byte> span)
-        {
-            byte firstByte = span[0];
-
-            ExceptionResource resource;
-            switch (firstByte)
-            {
-                case (byte)'t':
-                    resource = ExceptionResource.ExpectedTrue;
-                    break;
-                case (byte)'f':
-                    resource = ExceptionResource.ExpectedFalse;
-                    break;
-                default:
-                    Debug.Assert(firstByte == 'n');
-                    resource = ExceptionResource.ExpectedNull;
-                    break;
-            }
-            return ThrowHelper.GetJsonReaderException(ref this, resource, nextByte: default, bytes: span);
-        }
-
-        private bool ConsumeNumberMultiSegment()
-        {
-            if (!TryGetNumberMultiSegment(_buffer.Slice(_consumed), out int consumed))
-            {
-                return false;
-            }
-
-            _tokenType = JsonTokenType.Number;
-            _consumed += consumed;
-
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (!_isNotPrimitive)
-                {
-                    return true;
-                }
-                if (IsLastSpan || !GetNextSpan())
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed - 1]);
-                }
-            }
-
-            // TODO: https://github.com/dotnet/corefx/issues/33294
-            if (JsonConstants.Delimiters.IndexOf(_buffer[_consumed]) < 0)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed]);
-            }
-            return true;
-        }
-
-        private bool ConsumePropertyNameMultiSegment()
-        {
-            if (!ConsumeStringMultiSegment())
-            {
-                return false;
-            }
-
-            if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
-            {
-                return false;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
-            // Those cases are captured below where we only accept ':'.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpaceMultiSegment();
-                if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
-                {
-                    return false;
-                }
-                first = _buffer[_consumed];
-            }
-
-            // The next character must be a key / value seperator. Validate and skip.
-            if (first != JsonConstants.KeyValueSeperator)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedSeparatorAfterPropertyNameNotFound, first);
-            }
-
-            _consumed++;
-            _bytePositionInLine++;
-            _tokenType = JsonTokenType.PropertyName;
-            return true;
-        }
-
-        private bool ConsumeStringMultiSegment()
-        {
-            Debug.Assert(_buffer.Length >= _consumed + 1);
-            Debug.Assert(_buffer[_consumed] == JsonConstants.Quote);
-
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-
-            // Vectorized search for either quote, backslash, or any control character.
-            // If the first found byte is a quote, we have reached an end of string, and
-            // can avoid validation.
-            // Otherwise, in the uncommon case, iterate one character at a time and validate.
-            int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
-
-            if (idx >= 0)
-            {
-                byte foundByte = localBuffer[idx];
-                if (foundByte == JsonConstants.Quote)
-                {
-                    _bytePositionInLine += idx + 2; // Add 2 for the start and end quotes.
-                    ValueSpan = localBuffer.Slice(0, idx);
-                    HasValueSequence = false;
-                    _tokenType = JsonTokenType.String;
-                    _consumed += idx + 2;
-                    return true;
-                }
-                else
-                {
-                    return ConsumeStringAndValidateMultiSegment(localBuffer, idx);
-                }
-            }
-            else
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                }
-                return ConsumeStringNextSegment();
-            }
-        }
-
-        private bool ConsumeStringNextSegment()
-        {
-            SequencePosition startPosition = _currentPosition;
-            SequencePosition end = default;
-            int startConsumed = _consumed + 1;
-            HasValueSequence = true;
-            int leftOver = _buffer.Length - _consumed;
-
-            long prevTotalConsumed = _totalConsumed;
-            long prevPosition = _bytePositionInLine;
-
-            while (true)
-            {
-                if (!GetNextSpan())
-                {
-                    _totalConsumed = prevTotalConsumed;
-                    _bytePositionInLine = prevPosition;
-                    _consumed = startConsumed - 1;
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                    }
-                    return false;
-                }
-
-                //Create local copy to avoid bounds checks.
-                ReadOnlySpan<byte> localBuffer = _buffer;
-                int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
-
-                if (idx >= 0)
-                {
-                    byte foundByte = localBuffer[idx];
-                    if (foundByte == JsonConstants.Quote)
-                    {
-                        end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
-                        _bytePositionInLine += leftOver + idx + 1;  // Add 1 for the end quote of the string.
-                        _totalConsumed += leftOver;
-                        _consumed = idx + 1;    // Add 1 for the end quote of the string.
-                        break;
-                    }
-                    else
-                    {
-                        long prevLineNumber = _lineNumber;
-
-                        _bytePositionInLine += idx + 1; // Add 1 for the first quote
-
-                        bool nextCharEscaped = false;
-                        bool sawNewLine = false;
-                        while (true)
-                        {
-                        StartOfLoop:
-                            for (; idx < localBuffer.Length; idx++)
-                            {
-                                byte currentByte = localBuffer[idx];
-                                if (currentByte == JsonConstants.Quote)
-                                {
-                                    if (!nextCharEscaped)
-                                    {
-                                        goto Done;
-                                    }
-                                    nextCharEscaped = false;
-                                }
-                                else if (currentByte == JsonConstants.BackSlash)
-                                {
-                                    nextCharEscaped = !nextCharEscaped;
-                                }
-                                else if (nextCharEscaped)
-                                {
-                                    int index = JsonConstants.EscapableChars.IndexOf(currentByte);
-                                    if (index == -1)
-                                    {
-                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
-                                    }
-
-                                    if (currentByte == JsonConstants.Quote)
-                                    {
-                                        // Ignore an escaped quote.
-                                        // This is likely the most common case, so adding an explicit check
-                                        // to avoid doing the unnecessary checks below.
-                                    }
-                                    else if (currentByte == 'n')
-                                    {
-                                        // Escaped new line character
-                                        _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
-                                        _lineNumber++;
-                                        sawNewLine = true;
-                                    }
-                                    else if (currentByte == 'u')
-                                    {
-                                        // Expecting 4 hex digits to follow the escaped 'u'
-                                        _bytePositionInLine++;  // move past the 'u'
-
-                                        bool movedToNext = false;
-                                        int numberOfHexDigits = 3;
-                                        int j = idx + 1;
-                                        while (true)
-                                        {
-                                            for (; j < localBuffer.Length; j++)
-                                            {
-                                                byte nextByte = localBuffer[j];
-                                                if (!JsonReaderHelper.IsHexDigit(nextByte))
-                                                {
-                                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
-                                                }
-                                                if (j - idx > numberOfHexDigits)
-                                                {
-                                                    if (movedToNext)
-                                                    {
-                                                        nextCharEscaped = false;
-                                                        goto StartOfLoop;
-                                                    }
-                                                    goto DoneWithHex;
-                                                }
-                                                _bytePositionInLine++;
-                                            }
-
-                                            if (!GetNextSpan())
-                                            {
-                                                if (IsLastSpan)
-                                                {
-                                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                                                }
-
-                                                // We found less than 4 hex digits.
-                                                _lineNumber = prevLineNumber;
-                                                _bytePositionInLine = prevPosition;
-                                                return false;
-                                            }
-
-                                            _totalConsumed += localBuffer.Length;
-
-                                            localBuffer = _buffer;
-                                            idx = 0;
-                                            j = 0;
-                                            movedToNext = true;
-                                            numberOfHexDigits -= j - idx;
-                                        }
-
-                                    DoneWithHex:
-                                        idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
-                                    }
-                                    nextCharEscaped = false;
-                                }
-                                else if (currentByte < JsonConstants.Space)
-                                {
-                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
-                                }
-
-                                _bytePositionInLine++;
-                            }
-
-                            if (!GetNextSpan())
-                            {
-                                if (IsLastSpan)
-                                {
-                                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                                }
-                                _lineNumber = prevLineNumber;
-                                _bytePositionInLine = prevPosition;
-                                return false;
-                            }
-
-                            _totalConsumed += localBuffer.Length;
-                            localBuffer = _buffer;
-                            idx = 0;
-                        }
-
-                    Done:
-                        _bytePositionInLine += sawNewLine ? leftOver + idx : leftOver + idx + 1;  // Add 1 for the end quote of the string.
-                        _consumed = idx + 1;    // Add 1 for the end quote of the string.
-                        _totalConsumed += leftOver;
-                        end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
-                        break;
-                    }
-                }
-
-                _totalConsumed += localBuffer.Length;
-                _bytePositionInLine += localBuffer.Length;
-            }
-
-            SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
-            ValueSequence = _sequence.Slice(start, end);
-            _tokenType = JsonTokenType.String;
-            return true;
-        }
-
-        // Found a backslash or control characters which are considered invalid within a string.
-        // Search through the rest of the string one byte at a time.
-        // https://tools.ietf.org/html/rfc8259#section-7
-        private bool ConsumeStringAndValidateMultiSegment(ReadOnlySpan<byte> data, int idx)
-        {
-            Debug.Assert(idx >= 0 && idx < data.Length);
-            Debug.Assert(data[idx] != JsonConstants.Quote);
-            Debug.Assert(data[idx] == JsonConstants.BackSlash || data[idx] < JsonConstants.Space);
-
-            SequencePosition startPosition = _currentPosition;
-            SequencePosition end = default;
-            int startConsumed = _consumed + 1;
-            HasValueSequence = false;
-            int leftOver = _buffer.Length - idx;
-            int leftOverFromConsumed = _buffer.Length - _consumed;
-
-            long prevTotalConsumed = _totalConsumed;
-            long prevLineBytePosition = _bytePositionInLine;
-            long prevLineNumber = _lineNumber;
-
-            _bytePositionInLine += idx + 1; // Add 1 for the first quote
-
-            bool nextCharEscaped = false;
-            while (true)
-            {
-            StartOfLoop:
-                for (; idx < data.Length; idx++)
-                {
-                    byte currentByte = data[idx];
-                    if (currentByte == JsonConstants.Quote)
-                    {
-                        if (!nextCharEscaped)
-                        {
-                            goto Done;
-                        }
-                        nextCharEscaped = false;
-                    }
-                    else if (currentByte == JsonConstants.BackSlash)
-                    {
-                        nextCharEscaped = !nextCharEscaped;
-                    }
-                    else if (nextCharEscaped)
-                    {
-                        int index = JsonConstants.EscapableChars.IndexOf(currentByte);
-                        if (index == -1)
-                        {
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
-                        }
-
-                        if (currentByte == JsonConstants.Quote)
-                        {
-                            // Ignore an escaped quote.
-                            // This is likely the most common case, so adding an explicit check
-                            // to avoid doing the unnecessary checks below.
-                        }
-                        else if (currentByte == 'n')
-                        {
-                            // Escaped new line character
-                            _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
-                            _lineNumber++;
-                        }
-                        else if (currentByte == 'u')
-                        {
-                            // Expecting 4 hex digits to follow the escaped 'u'
-                            _bytePositionInLine++;  // move past the 'u'
-
-                            bool movedToNext = false;
-                            int numberOfHexDigits = 3;
-                            int j = idx + 1;
-                            while (true)
-                            {
-                                for (; j < data.Length; j++)
-                                {
-                                    byte nextByte = data[j];
-                                    if (!JsonReaderHelper.IsHexDigit(nextByte))
-                                    {
-                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
-                                    }
-                                    if (j - idx > numberOfHexDigits)
-                                    {
-                                        if (movedToNext)
-                                        {
-                                            nextCharEscaped = false;
-                                            goto StartOfLoop;
-                                        }
-                                        goto DoneWithHex;
-                                    }
-                                    _bytePositionInLine++;
-                                }
-
-                                if (!GetNextSpan())
-                                {
-                                    if (IsLastSpan)
-                                    {
-                                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                                    }
-
-                                    // We found less than 4 hex digits.
-                                    _lineNumber = prevLineNumber;
-                                    _bytePositionInLine = prevLineBytePosition;
-                                    return false;
-                                }
-
-                                // Do not add the left over for the first segment to total consumed
-                                if (HasValueSequence)
-                                {
-                                    _totalConsumed += data.Length;
-                                }
-
-                                data = _buffer;
-                                idx = 0;
-                                j = 0;
-                                HasValueSequence = true;
-                                movedToNext = true;
-                                numberOfHexDigits -= j - idx;
-                            }
-
-                        DoneWithHex:
-                            idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
-                        }
-                        nextCharEscaped = false;
-                    }
-                    else if (currentByte < JsonConstants.Space)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
-                    }
-
-                    _bytePositionInLine++;
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                    }
-                    _lineNumber = prevLineNumber;
-                    _bytePositionInLine = prevLineBytePosition;
-                    return false;
-                }
-
-                // Do not add the left over for the first segment to total consumed
-                if (HasValueSequence)
-                {
-                    _totalConsumed += data.Length;
-                }
-
-                data = _buffer;
-                idx = 0;
-                HasValueSequence = true;
-            }
-
-        Done:
-            if (HasValueSequence)
-            {
-                _bytePositionInLine += leftOver + idx + 1;  // Add 1 for the end quote of the string.
-                _consumed = idx + 1;    // Add 1 for the end quote of the string.
-                _totalConsumed += leftOverFromConsumed;
-                end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
-                SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
-                ValueSequence = _sequence.Slice(start, end);
-            }
-            else
-            {
-                _bytePositionInLine++;  // Add 1 for the end quote
-                _consumed += idx + 2;
-                ValueSpan = data.Slice(0, idx);
-            }
-            _tokenType = JsonTokenType.String;
-            return true;
-        }
-
-        // https://tools.ietf.org/html/rfc7159#section-6
-        private bool TryGetNumberMultiSegment(ReadOnlySpan<byte> data, out int consumed)
-        {
-            // TODO: https://github.com/dotnet/corefx/issues/33294
-            Debug.Assert(data.Length > 0);
-
-            _numberFormat = default;
-            SequencePosition startPosition = _currentPosition;
-            int startConsumed = _consumed;
-            consumed = 0;
-            long prevTotalConsumed = _totalConsumed;
-            long prevPosition = _bytePositionInLine;
-            int i = 0;
-
-            ConsumeNumberResult signResult = ConsumeNegativeSignMultiSegment(ref data, ref i);
-            if (signResult == ConsumeNumberResult.NeedMoreData)
-            {
-                _totalConsumed = prevTotalConsumed;
-                _bytePositionInLine = prevPosition;
-                _consumed = startConsumed;
-                return false;
-            }
-
-            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
-
-            byte nextByte = data[i];
-            Debug.Assert(nextByte >= '0' && nextByte <= '9');
-
-            if (nextByte == '0')
-            {
-                ConsumeNumberResult result = ConsumeZeroMultiSegment(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    _totalConsumed = prevTotalConsumed;
-                    _bytePositionInLine = prevPosition;
-                    _consumed = startConsumed;
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-            }
-            else
-            {
-                ConsumeNumberResult result = ConsumeIntegerDigitsMultiSegment(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    _totalConsumed = prevTotalConsumed;
-                    _bytePositionInLine = prevPosition;
-                    _consumed = startConsumed;
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-                if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
-                }
-            }
-
-            Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');
-
-            if (nextByte == '.')
-            {
-                i++;
-                _bytePositionInLine++;
-                ConsumeNumberResult result = ConsumeDecimalDigitsMultiSegment(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    _totalConsumed = prevTotalConsumed;
-                    _bytePositionInLine = prevPosition;
-                    _consumed = startConsumed;
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-                if (nextByte != 'E' && nextByte != 'e')
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
-                }
-            }
-
-            Debug.Assert(nextByte == 'E' || nextByte == 'e');
-            i++;
-            _numberFormat = 'e';
-            _bytePositionInLine++;
-
-            signResult = ConsumeSignMultiSegment(ref data, ref i);
-            if (signResult == ConsumeNumberResult.NeedMoreData)
-            {
-                _totalConsumed = prevTotalConsumed;
-                _bytePositionInLine = prevPosition;
-                _consumed = startConsumed;
-                return false;
-            }
-
-            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
-
-            i++;
-            _bytePositionInLine++;
-            ConsumeNumberResult resultExponent = ConsumeIntegerDigitsMultiSegment(ref data, ref i);
-            if (resultExponent == ConsumeNumberResult.NeedMoreData)
-            {
-                _totalConsumed = prevTotalConsumed;
-                _bytePositionInLine = prevPosition;
-                _consumed = startConsumed;
-                return false;
-            }
-            if (resultExponent == ConsumeNumberResult.Success)
-            {
-                goto Done;
-            }
-
-            Debug.Assert(resultExponent == ConsumeNumberResult.OperationIncomplete);
-
-            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, data[i]);
-
-        Done:
-            if (HasValueSequence)
-            {
-                SequencePosition start = new SequencePosition(startPosition.GetObject(), startPosition.GetInteger() + startConsumed);
-                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + i);
-                ValueSequence = _sequence.Slice(start, end);
-                consumed = i;
-            }
-            else
-            {
-                ValueSpan = data.Slice(0, i);
-                consumed = i;
-            }
-            return true;
-        }
-
-        private ConsumeNumberResult ConsumeNegativeSignMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            byte nextByte = data[i];
-
-            if (nextByte == '-')
-            {
-                i++;
-                _bytePositionInLine++;
-                if (i >= data.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                        }
-                        return ConsumeNumberResult.NeedMoreData;
-                    }
-                    _totalConsumed++;
-                    HasValueSequence = true;
-                    i = 0;
-                    data = _buffer;
-                }
-
-                nextByte = data[i];
-                if (!JsonReaderHelper.IsDigit(nextByte))
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
-                }
-            }
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeZeroMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            Debug.Assert(data[i] == (byte)'0');
-            i++;
-            _bytePositionInLine++;
-            byte nextByte = default;
-            if (i < data.Length)
-            {
-                nextByte = data[i];
-                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
-                {
-                    return ConsumeNumberResult.Success;
-                }
-            }
-            else
-            {
-                if (IsLastSpan)
-                {
-                    // A payload containing a single value: "0" is valid
-                    // If we are v with multi-value JSON,
-                    // ConsumeNumber will validate that we have a delimiter following the "0".
-                    return ConsumeNumberResult.Success;
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        return ConsumeNumberResult.Success;
-                    }
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-
-                _totalConsumed++;
-                HasValueSequence = true;
-                i = 0;
-                data = _buffer;
-                nextByte = data[i];
-                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
-                {
-                    return ConsumeNumberResult.Success;
-                }
-            }
-            nextByte = data[i];
-            if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeIntegerDigitsMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            byte nextByte = default;
-            int counter = 0;
-            for (; i < data.Length; i++)
-            {
-                nextByte = data[i];
-                if (!JsonReaderHelper.IsDigit(nextByte))
-                {
-                    break;
-                }
-                counter++;
-            }
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    // A payload containing a single value of integers (e.g. "12") is valid
-                    // If we are dealing with multi-value JSON,
-                    // ConsumeNumber will validate that we have a delimiter following the integer.
-                    _bytePositionInLine += counter;
-                    return ConsumeNumberResult.Success;
-                }
-
-                while (true)
-                {
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            _bytePositionInLine += counter;
-                            return ConsumeNumberResult.Success;
-                        }
-                        return ConsumeNumberResult.NeedMoreData;
-                    }
-
-                    _totalConsumed += i;
-                    _bytePositionInLine += counter;
-                    counter = 0;
-                    HasValueSequence = true;
-                    i = 0;
-                    data = _buffer;
-                    for (; i < data.Length; i++)
-                    {
-                        nextByte = data[i];
-                        if (!JsonReaderHelper.IsDigit(nextByte))
-                        {
-                            break;
-                        }
-                    }
-                    _bytePositionInLine += i;
-                    if (i >= data.Length)
-                    {
-                        if (IsLastSpan)
-                        {
-                            return ConsumeNumberResult.Success;
-                        }
-                    }
-                    else
-                    {
-                        break;
-                    }
-                }
-
-            }
-            else
-            {
-                _bytePositionInLine += counter;
-            }
-
-            if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
-            {
-                return ConsumeNumberResult.Success;
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeDecimalDigitsMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                }
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-                _totalConsumed += i;
-                HasValueSequence = true;
-                i = 0;
-                data = _buffer;
-            }
-            byte nextByte = data[i];
-            if (!JsonReaderHelper.IsDigit(nextByte))
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterDecimal, nextByte);
-            }
-            i++;
-            _bytePositionInLine++;
-            return ConsumeIntegerDigitsMultiSegment(ref data, ref i);
-        }
-
-        private ConsumeNumberResult ConsumeSignMultiSegment(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-                HasValueSequence = true;
-                i = 0;
-                data = _buffer;
-            }
-
-            byte nextByte = data[i];
-            if (nextByte == '+' || nextByte == '-')
-            {
-                i++;
-                _bytePositionInLine++;
-                if (i >= data.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                        }
-                        return ConsumeNumberResult.NeedMoreData;
-                    }
-                    _totalConsumed++;
-                    HasValueSequence = true;
-                    i = 0;
-                    data = _buffer;
-                }
-                nextByte = data[i];
-            }
-
-            if (!JsonReaderHelper.IsDigit(nextByte))
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private bool ConsumeNextTokenOrRollbackMultiSegment(byte marker)
-        {
-            long prevTotalConsumed = _totalConsumed;
-            int prevConsumed = _consumed;
-            long prevPosition = _bytePositionInLine;
-            long prevLineNumber = _lineNumber;
-            JsonTokenType prevTokenType = _tokenType;
-            ConsumeTokenResult result = ConsumeNextTokenMultiSegment(marker);
-            if (result == ConsumeTokenResult.Success)
-            {
-                return true;
-            }
-            if (result == ConsumeTokenResult.NotEnoughDataRollBackState)
-            {
-                _consumed = prevConsumed;
-                _tokenType = prevTokenType;
-                _bytePositionInLine = prevPosition;
-                _lineNumber = prevLineNumber;
-                _totalConsumed = prevTotalConsumed;
-            }
-            return false;
-        }
-
-        /// <summary>
-        /// This method consumes the next token regardless of whether we are inside an object or an array.
-        /// For an object, it reads the next property name token. For an array, it just reads the next value.
-        /// </summary>
-        private ConsumeTokenResult ConsumeNextTokenMultiSegment(byte marker)
-        {
-            if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
-            {
-                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
-                {
-                    if (marker == JsonConstants.Slash)
-                    {
-                        return ConsumeCommentMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    if (_tokenType == JsonTokenType.Comment)
-                    {
-                        return ConsumeNextTokenFromLastNonCommentTokenMultiSegment();
-                    }
-                }
-                else
-                {
-                    Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                    return ConsumeNextTokenUntilAfterAllCommentsAreSkippedMultiSegment(marker);
-                }
-            }
-
-            if (!_isNotPrimitive)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
-            }
-
-            if (marker == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            _consumed--;
-                            _bytePositionInLine--;
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                        }
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                }
-                byte first = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (first <= JsonConstants.Space)
-                {
-                    SkipWhiteSpaceMultiSegment();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    first = _buffer[_consumed];
-                }
-
-                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash)
-                {
-                    return ConsumeCommentMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-
-                if (_inObject)
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-                    return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                else
-                {
-                    return ConsumeValueMultiSegment(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-            }
-            else if (marker == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (marker == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
-            }
-            return ConsumeTokenResult.Success;
-        }
-
-        private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment()
-        {
-            if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType))
-            {
-                _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray;
-            }
-            else
-            {
-                _tokenType = _previousTokenType;
-            }
-
-            Debug.Assert(_tokenType != JsonTokenType.Comment);
-
-            if (!HasMoreDataMultiSegment())
-            {
-                goto RollBack;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpaceMultiSegment();
-                if (!HasMoreDataMultiSegment())
-                {
-                    goto RollBack;
-                }
-                first = _buffer[_consumed];
-            }
-
-            if (!_isNotPrimitive && _tokenType != JsonTokenType.None)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, first);
-            }
-
-            Debug.Assert(first != JsonConstants.Slash);
-
-            if (first == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            _consumed--;
-                            _bytePositionInLine--;
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                        }
-                        goto RollBack;
-                    }
-                }
-                first = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (first <= JsonConstants.Space)
-                {
-                    SkipWhiteSpaceMultiSegment();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        goto RollBack;
-                    }
-                    first = _buffer[_consumed];
-                }
-
-                if (_inObject)
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-                    if (ConsumePropertyNameMultiSegment())
-                    {
-                        goto Done;
-                    }
-                    else
-                    {
-                        goto RollBack;
-                    }
-                }
-                else
-                {
-                    if (ConsumeValueMultiSegment(first))
-                    {
-                        goto Done;
-                    }
-                    else
-                    {
-                        goto RollBack;
-                    }
-                }
-            }
-            else if (first == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (first == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else if (_tokenType == JsonTokenType.None)
-            {
-                if (ReadFirstTokenMultiSegment(first))
-                {
-                    goto Done;
-                }
-                else
-                {
-                    goto RollBack;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (first == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-
-                    long prevTotalConsumed = _totalConsumed;
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    if (!ConsumePropertyNameMultiSegment())
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                        _totalConsumed = prevTotalConsumed;
-                        goto RollBack;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (first == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    if (!ConsumeValueMultiSegment(first))
-                    {
-                        goto RollBack;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                if (!ConsumeValueMultiSegment(first))
-                {
-                    goto RollBack;
-                }
-                goto Done;
-            }
-            else
-            {
-                goto RollBack;
-            }
-
-        Done:
-            return ConsumeTokenResult.Success;
-
-        RollBack:
-            return ConsumeTokenResult.NotEnoughDataRollBackState;
-        }
-
-        private bool SkipAllCommentsMultiSegment(ref byte marker)
-        {
-            while (marker == JsonConstants.Slash)
-            {
-                if (SkipCommentMultiSegment())
-                {
-                    if (!HasMoreDataMultiSegment())
-                    {
-                        goto IncompleteNoRollback;
-                    }
-
-                    marker = _buffer[_consumed];
-
-                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                    if (marker <= JsonConstants.Space)
-                    {
-                        SkipWhiteSpaceMultiSegment();
-                        if (!HasMoreDataMultiSegment())
-                        {
-                            goto IncompleteNoRollback;
-                        }
-                        marker = _buffer[_consumed];
-                    }
-                }
-                else
-                {
-                    goto IncompleteNoRollback;
-                }
-            }
-            return true;
-
-        IncompleteNoRollback:
-            return false;
-        }
-
-        private bool SkipAllCommentsMultiSegment(ref byte marker, ExceptionResource resource)
-        {
-            while (marker == JsonConstants.Slash)
-            {
-                if (SkipCommentMultiSegment())
-                {
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreDataMultiSegment(resource))
-                    {
-                        goto IncompleteRollback;
-                    }
-
-                    marker = _buffer[_consumed];
-
-                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                    if (marker <= JsonConstants.Space)
-                    {
-                        SkipWhiteSpaceMultiSegment();
-                        // The next character must be a start of a property name or value.
-                        if (!HasMoreDataMultiSegment(resource))
-                        {
-                            goto IncompleteRollback;
-                        }
-                        marker = _buffer[_consumed];
-                    }
-                }
-                else
-                {
-                    goto IncompleteRollback;
-                }
-            }
-            return true;
-
-        IncompleteRollback:
-            return false;
-        }
-
-        private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkippedMultiSegment(byte marker)
-        {
-            if (!SkipAllCommentsMultiSegment(ref marker))
-            {
-                goto IncompleteNoRollback;
-            }
-
-            if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (marker == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (marker != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
-                    }
-
-                    long prevTotalConsumed = _totalConsumed;
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    if (!ConsumePropertyNameMultiSegment())
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                        _totalConsumed = prevTotalConsumed;
-                        goto IncompleteNoRollback;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (marker == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    if (!ConsumeValueMultiSegment(marker))
-                    {
-                        goto IncompleteNoRollback;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                if (!ConsumeValueMultiSegment(marker))
-                {
-                    goto IncompleteNoRollback;
-                }
-                goto Done;
-            }
-            else if (!_isNotPrimitive)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
-            }
-            else if (marker == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    if (!GetNextSpan())
-                    {
-                        if (IsLastSpan)
-                        {
-                            _consumed--;
-                            _bytePositionInLine--;
-                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                        }
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                }
-                marker = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (marker <= JsonConstants.Space)
-                {
-                    SkipWhiteSpaceMultiSegment();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreDataMultiSegment(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    marker = _buffer[_consumed];
-                }
-
-                if (!SkipAllCommentsMultiSegment(ref marker, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                {
-                    goto IncompleteRollback;
-                }
-
-                if (_inObject)
-                {
-                    if (marker != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
-                    }
-                    return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                else
-                {
-                    return ConsumeValueMultiSegment(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-            }
-            else if (marker == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (marker == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
-            }
-
-        Done:
-            return ConsumeTokenResult.Success;
-        IncompleteNoRollback:
-            return ConsumeTokenResult.IncompleteNoRollBackNecessary;
-        IncompleteRollback:
-            return ConsumeTokenResult.NotEnoughDataRollBackState;
-        }
-
-        private bool SkipCommentMultiSegment()
-        {
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-            int leftOver = 2;
-
-            if (localBuffer.Length == 0)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-                    }
-                    return false;
-                }
-
-                _totalConsumed++;
-                _bytePositionInLine++;
-                localBuffer = _buffer;
-                leftOver = 1;
-            }
-
-            byte marker = localBuffer[0];
-
-            if (marker == JsonConstants.Slash)
-            {
-                return SkipSingleLineCommentMultiSegment(localBuffer.Slice(1), leftOver);
-            }
-
-            if (marker != JsonConstants.Asterisk)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-            }
-
-            return SkipMultiLineCommentMultiSegment(localBuffer.Slice(1), leftOver);
-        }
-
-        private bool SkipSingleLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver)
-        {
-            long prevTotalConsumed = _totalConsumed;
-            int idx;
-            do
-            {
-                // TODO: https://github.com/dotnet/corefx/issues/33293
-                idx = localBuffer.IndexOf(JsonConstants.LineFeed);
-                if (idx == -1)
-                {
-                    if (IsLastSpan)
-                    {
-                        idx = localBuffer.Length;
-                        // Assume everything on this line is a comment and there is no more data.
-                        _bytePositionInLine += 2 + localBuffer.Length;
-                        goto Done;
-                    }
-
-                    if (!GetNextSpan())
-                    {
-                        _totalConsumed = prevTotalConsumed;
-                        return false;
-                    }
-                    _totalConsumed += localBuffer.Length + leftOver;
-                    leftOver = 0;
-                    localBuffer = _buffer;
-                }
-            } while (idx == -1);
-
-            idx++;
-            _bytePositionInLine = 0;
-            _lineNumber++;
-        Done:
-            _consumed += leftOver + idx;
-            return true;
-        }
-
-        private bool SkipMultiLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver)
-        {
-            long prevTotalConsumed = _totalConsumed;
-            int i;
-            bool lastAsterisk = false;
-            while (true)
-            {
-                i = 0;
-                for (; i < localBuffer.Length; i++)
-                {
-                    byte nextByte = localBuffer[i];
-
-                    if (nextByte == JsonConstants.Slash && lastAsterisk)
-                    {
-                        goto Done;
-                    }
-
-                    if (nextByte == JsonConstants.Asterisk)
-                    {
-                        i++;
-                        lastAsterisk = true;
-                        if (i < localBuffer.Length)
-                        {
-                            if (localBuffer[i] == JsonConstants.Slash)
-                            {
-                                goto Done;
-                            }
-                        }
-                        else
-                        {
-                            if (!GetNextSpan())
-                            {
-                                _totalConsumed = prevTotalConsumed;
-                                return false;
-                            }
-                            _totalConsumed += localBuffer.Length + leftOver;
-                            _bytePositionInLine += localBuffer.Length + leftOver;
-                            leftOver = 0;
-                            localBuffer = _buffer;
-                            i = 0;
-                            if (localBuffer[i] == JsonConstants.Slash)
-                            {
-                                goto Done;
-                            }
-                            break;
-                        }
-                    }
-                    else if (nextByte == JsonConstants.LineFeed)
-                    {
-                        _bytePositionInLine = 0;
-                        _lineNumber++;
-                        lastAsterisk = false;
-                    }
-                    else
-                    {
-                        lastAsterisk = false;
-                    }
-                }
-                if (i == localBuffer.Length)
-                {
-                    if (!GetNextSpan())
-                    {
-                        _totalConsumed = prevTotalConsumed;
-                        return false;
-                    }
-                    _totalConsumed += localBuffer.Length + leftOver;
-                    _bytePositionInLine += localBuffer.Length + leftOver;
-                    leftOver = 0;
-                    localBuffer = _buffer;
-                }
-            }
-
-        Done:
-            _consumed += i + 1 + leftOver;
-            _bytePositionInLine += i + 1 + leftOver;
-            return true;
-        }
-
-        private bool ConsumeCommentMultiSegment()
-        {
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-            int leftOver = 2;
-
-            SequencePosition start = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + _consumed);
-            if (localBuffer.Length == 0)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-                }
-
-                if (!GetNextSpan())
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-                    }
-                    return false;
-                }
-
-                _totalConsumed++;
-                _bytePositionInLine++;
-                localBuffer = _buffer;
-                leftOver = 1;
-            }
-
-            byte marker = localBuffer[0];
-
-            if (marker == JsonConstants.Slash)
-            {
-                return ConsumeSingleLineCommentMultiSegment(localBuffer.Slice(1), leftOver, start, _consumed);
-            }
-
-            if (marker != JsonConstants.Asterisk)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-            }
-
-            return ConsumeMultiLineCommentMultiSegment(localBuffer.Slice(1), leftOver, start, _consumed);
-        }
-
-        private bool ConsumeSingleLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver, SequencePosition start, int previousConsumed)
-        {
-            long prevTotalConsumed = _totalConsumed;
-            int idx;
-            do
-            {
-                // TODO: https://github.com/dotnet/corefx/issues/33293
-                idx = localBuffer.IndexOf(JsonConstants.LineFeed);
-                if (idx == -1)
-                {
-                    if (IsLastSpan)
-                    {
-                        idx = localBuffer.Length;
-                        // Assume everything on this line is a comment and there is no more data.
-                        _bytePositionInLine += 2 + localBuffer.Length;
-                        goto Done;
-                    }
-
-                    if (!GetNextSpan())
-                    {
-                        _totalConsumed = prevTotalConsumed;
-                        return false;
-                    }
-                    HasValueSequence = true;
-                    _totalConsumed += localBuffer.Length + leftOver;
-                    leftOver = 0;
-                    localBuffer = _buffer;
-                }
-            } while (idx == -1);
-
-            idx++;
-            _bytePositionInLine = 0;
-            _lineNumber++;
-
-        Done:
-            if (HasValueSequence)
-            {
-                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + idx);
-                ReadOnlySequence<byte> commentSequence = _sequence.Slice(start, end);
-                if (commentSequence.IsSingleSegment)
-                {
-                    ValueSpan = commentSequence.First.Span;
-                    HasValueSequence = false;
-                }
-                else
-                {
-                    ValueSequence = commentSequence;
-                }
-            }
-            else
-            {
-                ValueSpan = _buffer.Slice(previousConsumed, idx + 2);   // Include the double slash and potential line feed at the end of the comment as part of it.
-            }
-
-            if (_tokenType != JsonTokenType.Comment)
-            {
-                _previousTokenType = _tokenType;
-            }
-            _tokenType = JsonTokenType.Comment;
-            _consumed += leftOver + idx;
-            return true;
-        }
-
-        private bool ConsumeMultiLineCommentMultiSegment(ReadOnlySpan<byte> localBuffer, int leftOver, SequencePosition start, int previousConsumed)
-        {
-            long prevTotalConsumed = _totalConsumed;
-            int i;
-            int lastLineFeedIndex;
-            bool lastAsterisk = false;
-            while (true)
-            {
-                i = 0;
-                lastLineFeedIndex = -1;
-                for (; i < localBuffer.Length; i++)
-                {
-                    byte nextByte = localBuffer[i];
-
-                    if (nextByte == JsonConstants.Slash && lastAsterisk)
-                    {
-                        goto Done;
-                    }
-
-                    if (nextByte == JsonConstants.Asterisk)
-                    {
-                        i++;
-                        lastAsterisk = true;
-                        if (i < localBuffer.Length)
-                        {
-                            if (localBuffer[i] == JsonConstants.Slash)
-                            {
-                                goto Done;
-                            }
-                        }
-                        else
-                        {
-                            if (!GetNextSpan())
-                            {
-                                _totalConsumed = prevTotalConsumed;
-                                return false;
-                            }
-                            HasValueSequence = true;
-                            _totalConsumed += localBuffer.Length + leftOver;
-
-                            if (lastLineFeedIndex == -1)
-                            {
-                                _bytePositionInLine += localBuffer.Length + leftOver;
-                            }
-                            else
-                            {
-                                _bytePositionInLine += i - lastLineFeedIndex - 1;
-                            }
-                            lastLineFeedIndex = -1;
-                            leftOver = 0;
-                            localBuffer = _buffer;
-                            i = 0;
-                            if (localBuffer[i] == JsonConstants.Slash)
-                            {
-                                goto Done;
-                            }
-                            break;
-                        }
-                    }
-                    else if (nextByte == JsonConstants.LineFeed)
-                    {
-                        lastLineFeedIndex = i;
-                        _bytePositionInLine = 0;
-                        _lineNumber++;
-                        lastAsterisk = false;
-                    }
-                    else
-                    {
-                        lastAsterisk = false;
-                    }
-                }
-                if (i == localBuffer.Length)
-                {
-                    if (!GetNextSpan())
-                    {
-                        _totalConsumed = prevTotalConsumed;
-                        return false;
-                    }
-                    HasValueSequence = true;
-                    _totalConsumed += localBuffer.Length + leftOver;
-                    if (lastLineFeedIndex == -1)
-                    {
-                        _bytePositionInLine += localBuffer.Length + leftOver;
-                    }
-                    else
-                    {
-                        _bytePositionInLine += i - lastLineFeedIndex - 1;
-                    }
-                    lastLineFeedIndex = -1;
-                    leftOver = 0;
-                    localBuffer = _buffer;
-                }
-            }
-
-        Done:
-            if (HasValueSequence)
-            {
-                // Include the final slash at the end of the comment as part of it.
-                SequencePosition end = new SequencePosition(_currentPosition.GetObject(), _currentPosition.GetInteger() + i + 1);
-                ReadOnlySequence<byte> commentSequence = _sequence.Slice(start, end);
-                if (commentSequence.IsSingleSegment)
-                {
-                    ValueSpan = commentSequence.First.Span;
-                    HasValueSequence = false;
-                }
-                else
-                {
-                    ValueSequence = commentSequence;
-                }
-            }
-            else
-            {
-
-                ValueSpan = _buffer.Slice(previousConsumed, i + 3); // Include the slash/asterisk and final slash at the end of the comment as part of it.
-            }
-
-            if (_tokenType != JsonTokenType.Comment)
-            {
-                _previousTokenType = _tokenType;
-            }
-            _tokenType = JsonTokenType.Comment;
-            _consumed += i + 1 + leftOver;
-            if (lastLineFeedIndex == -1)
-            {
-                _bytePositionInLine += i + 1 + leftOver;
-            }
-            else
-            {
-                _bytePositionInLine += i - lastLineFeedIndex;
-            }
-            return true;
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.TryGet.cs
deleted file mode 100644 (file)
index 4fee0c9..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Buffers.Text;
-using System.Diagnostics;
-
-namespace System.Text.Json
-{
-    public ref partial struct Utf8JsonReader
-    {
-        // Reject any invalid UTF-8 data rather than silently replacing.
-        private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
-
-        /// <summary>
-        /// Reads the next JSON token value from the source transcoded as a <see cref="string"/>.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of the JSON token that is not a string
-        /// (i.e. other than <see cref="JsonTokenType.String"/> or <see cref="JsonTokenType.PropertyName"/>).
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        /// <exception cref="ArgumentException">
-        /// Thrown if invalid UTF-8 byte sequences are detected while transcoding.
-        /// </exception>
-        public string GetStringValue()
-        {
-            if (TokenType != JsonTokenType.String && TokenType != JsonTokenType.PropertyName)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-
-            // TODO: https://github.com/dotnet/corefx/issues/33292
-            return s_utf8Encoding.GetString(span);
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source as a <see cref="bool"/>.
-        /// Returns true if the TokenType is JsonTokenType.True and false if the TokenType is JsonTokenType.False.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a boolean (i.e. <see cref="JsonTokenType.True"/> or <see cref="JsonTokenType.False"/>).
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool GetBooleanValue()
-        {
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-
-            if (TokenType == JsonTokenType.True)
-            {
-                Debug.Assert(span.Length == 4);
-                return true;
-            }
-            else if (TokenType == JsonTokenType.False)
-            {
-                Debug.Assert(span.Length == 5);
-                return false;
-            }
-            else
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedBoolean(TokenType);
-            }
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source and parses it to a <see cref="int"/>.
-        /// Returns true if the entire UTF-8 encoded token value can be successfully 
-        /// parsed to a <see cref="int"/> value.
-        /// Returns false otherwise.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool TryGetInt32Value(out int value)
-        {
-            if (TokenType != JsonTokenType.Number)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-            return Utf8Parser.TryParse(span, out value, out int bytesConsumed) && span.Length == bytesConsumed;
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source and parses it to a <see cref="long"/>.
-        /// Returns true if the entire UTF-8 encoded token value can be successfully 
-        /// parsed to a <see cref="long"/> value.
-        /// Returns false otherwise.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool TryGetInt64Value(out long value)
-        {
-            if (TokenType != JsonTokenType.Number)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-            return Utf8Parser.TryParse(span, out value, out int bytesConsumed) && span.Length == bytesConsumed;
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source and parses it to a <see cref="float"/>.
-        /// Returns true if the entire UTF-8 encoded token value can be successfully 
-        /// parsed to a <see cref="float"/> value.
-        /// Returns false otherwise.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool TryGetSingleValue(out float value)
-        {
-            if (TokenType != JsonTokenType.Number)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source and parses it to a <see cref="double"/>.
-        /// Returns true if the entire UTF-8 encoded token value can be successfully 
-        /// parsed to a <see cref="double"/> value.
-        /// Returns false otherwise.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool TryGetDoubleValue(out double value)
-        {
-            if (TokenType != JsonTokenType.Number)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
-        }
-
-        /// <summary>
-        /// Reads the next JSON token value from the source and parses it to a <see cref="decimal"/>.
-        /// Returns true if the entire UTF-8 encoded token value can be successfully 
-        /// parsed to a <see cref="decimal"/> value.
-        /// Returns false otherwise.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">
-        /// Thrown if trying to get the value of JSON token that is not a <see cref="JsonTokenType.Number"/>.
-        /// <seealso cref="TokenType" />
-        /// </exception>
-        public bool TryGetDecimalValue(out decimal value)
-        {
-            if (TokenType != JsonTokenType.Number)
-            {
-                throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType);
-            }
-
-            ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
-            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed;
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Utf8JsonReader.cs
deleted file mode 100644 (file)
index 708f3e6..0000000
+++ /dev/null
@@ -1,1820 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Buffers;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace System.Text.Json
-{
-    /// <summary>
-    /// Provides a high-performance API for forward-only, read-only access to the UTF-8 encoded JSON text.
-    /// It processes the text sequentially with no caching and adheres strictly to the JSON RFC
-    /// by default (https://tools.ietf.org/html/rfc8259). When it encounters invalid JSON, it throws
-    /// a JsonReaderException with basic error information like line number and byte position on the line.
-    /// Since this type is a ref struct, it does not directly support async. However, it does provide
-    /// support for reentrancy to read incomplete data, and continue reading once more data is presented.
-    /// To be able to set max depth while reading OR allow skipping comments, create an instance of 
-    /// <see cref="JsonReaderState"/> and pass that in to the reader.
-    /// </summary>
-    public ref partial struct Utf8JsonReader
-    {
-        private ReadOnlySpan<byte> _buffer;
-
-        private bool _isFinalBlock;
-
-        private long _lineNumber;
-        private long _bytePositionInLine;
-        private int _consumed;
-        private int _maxDepth;
-        private bool _inObject;
-        private bool _isNotPrimitive;
-        private char _numberFormat;
-        private JsonTokenType _tokenType;
-        private JsonTokenType _previousTokenType;
-        private JsonReaderOptions _readerOptions;
-        private BitStack _bitStack;
-
-        private long _totalConsumed;
-        private bool _isLastSegment;
-        private readonly bool _isSingleSegment;
-
-        private SequencePosition _nextPosition;
-        private SequencePosition _currentPosition;
-        private ReadOnlySequence<byte> _sequence;
-
-        private bool IsLastSpan => _isFinalBlock && (_isSingleSegment || _isLastSegment);
-
-        /// <summary>
-        /// Gets the value of the last processed token as a ReadOnlySpan&lt;byte&gt; slice
-        /// of the input payload. If the JSON is provided within a ReadOnlySequence&lt;byte&gt;
-        /// and the slice that represents the token value fits in a single segment, then
-        /// <see cref="ValueSpan"/> will contain the sliced value since it can be represented as a span.
-        /// Otherwise, the <see cref="ValueSequence"/> will contain the token value.
-        /// </summary>
-        /// <remarks>
-        /// If <see cref="HasValueSequence"/> is true, <see cref="ValueSpan"/> contains useless data, likely for
-        /// a previous single-segment token. Therefore, only access <see cref="ValueSpan"/> if <see cref="HasValueSequence"/> is false.
-        /// Otherwise, the token value must be accessed from <see cref="ValueSequence"/>.
-        /// </remarks>
-        public ReadOnlySpan<byte> ValueSpan { get; private set; }
-
-        /// <summary>
-        /// Returns the total amount of bytes consumed by the <see cref="Utf8JsonReader"/> so far
-        /// for the current instance of the <see cref="Utf8JsonReader"/> with the given UTF-8 encoded input text.
-        /// </summary>
-        public long BytesConsumed => _totalConsumed + _consumed;
-
-        /// <summary>
-        /// Tracks the recursive depth of the nested objects / arrays within the JSON text
-        /// processed so far. This provides the depth of the current token.
-        /// </summary>
-        public int CurrentDepth => _bitStack.CurrentDepth;
-
-        /// <summary>
-        /// Gets the type of the last processed JSON token in the UTF-8 encoded JSON text.
-        /// </summary>
-        public JsonTokenType TokenType => _tokenType;
-
-        /// <summary>
-        /// Lets the caller know which of the two 'Value' properties to read to get the 
-        /// token value. For input data within a ReadOnlySpan&lt;byte&gt; this will
-        /// always return false. For input data within a ReadOnlySequence&lt;byte&gt;, this
-        /// will only return true if the token value straddles more than a single segment and
-        /// hence couldn't be represented as a span.
-        /// </summary>
-        public bool HasValueSequence { get; private set; }
-
-        /// <summary>
-        /// Gets the value of the last processed token as a ReadOnlySpan&lt;byte&gt; slice
-        /// of the input payload. If the JSON is provided within a ReadOnlySequence&lt;byte&gt;
-        /// and the slice that represents the token value fits in a single segment, then
-        /// <see cref="ValueSpan"/> will contain the sliced value since it can be represented as a span.
-        /// Otherwise, the <see cref="ValueSequence"/> will contain the token value.
-        /// </summary>
-        /// <remarks>
-        /// If <see cref="HasValueSequence"/> is false, <see cref="ValueSequence"/> contains useless data, likely for
-        /// a previous multi-segment token. Therefore, only access <see cref="ValueSpan"/> if <see cref="HasValueSequence"/> is true.
-        /// Otherwise, the token value must be accessed from <see cref="ValueSpan"/>.
-        /// </remarks>
-        public ReadOnlySequence<byte> ValueSequence { get; private set; }
-
-        /// <summary>
-        /// Returns the current <see cref="SequencePosition"/> within the provided UTF-8 encoded
-        /// input ReadOnlySequence&lt;byte&gt;. If the <see cref="Utf8JsonReader"/> was constructed
-        /// with a ReadOnlySpan&lt;byte&gt; instead, this will always return a default <see cref="SequencePosition"/>.
-        /// </summary>
-        public SequencePosition Position
-        {
-            get
-            {
-                // TODO: Cannot use Slice even though it would be faster: https://github.com/dotnet/corefx/issues/33291
-                return _currentPosition.GetObject() == null
-                    ? default
-                    : _sequence.GetPosition(BytesConsumed);
-            }
-        }
-
-        /// <summary>
-        /// Returns the current snapshot of the <see cref="Utf8JsonReader"/> state which must
-        /// be captured by the caller and passed back in to the <see cref="Utf8JsonReader"/> ctor with more data.
-        /// Unlike the <see cref="Utf8JsonReader"/>, 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="Utf8JsonReader"/>.
-        /// </summary>
-        public JsonReaderState CurrentState => new JsonReaderState
-        {
-            _lineNumber = _lineNumber,
-            _bytePositionInLine = _bytePositionInLine,
-            _bytesConsumed = BytesConsumed,
-            _maxDepth = _maxDepth,
-            _inObject = _inObject,
-            _isNotPrimitive = _isNotPrimitive,
-            _numberFormat = _numberFormat,
-            _tokenType = _tokenType,
-            _previousTokenType = _previousTokenType,
-            _readerOptions = _readerOptions,
-            _bitStack = _bitStack,
-        };
-
-        /// <summary>
-        /// Constructs a new <see cref="Utf8JsonReader"/> instance.
-        /// </summary>
-        /// <param name="jsonData">The ReadOnlySpan&lt;byte&gt; containing the UTF-8 encoded JSON text to process.</param>
-        /// <param name="isFinalBlock">True when the input span contains the entire data to process.
-        /// Set to false only if it is known that the input span contains partial data with more data to follow.</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="Utf8JsonReader"/> and pass that back.</param>
-        /// <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="JsonReaderState"/>.
-        /// </remarks>
-        public Utf8JsonReader(ReadOnlySpan<byte> jsonData, bool isFinalBlock, JsonReaderState state)
-        {
-            _buffer = jsonData;
-
-            _isFinalBlock = isFinalBlock;
-
-            // Note: We do not retain _bytesConsumed or _sequencePosition as they reset with the new input data
-            _lineNumber = state._lineNumber;
-            _bytePositionInLine = state._bytePositionInLine;
-            _maxDepth = state._maxDepth == 0 ? JsonReaderState.DefaultMaxDepth : state._maxDepth; // If max depth is not set, revert to the default depth.
-            _inObject = state._inObject;
-            _isNotPrimitive = state._isNotPrimitive;
-            _numberFormat = state._numberFormat;
-            _tokenType = state._tokenType;
-            _previousTokenType = state._previousTokenType;
-            _readerOptions = state._readerOptions;
-            _bitStack = state._bitStack;
-
-            _consumed = 0;
-            _totalConsumed = 0;
-            _isLastSegment = _isFinalBlock;
-            _isSingleSegment = true;
-
-            ValueSpan = ReadOnlySpan<byte>.Empty;
-
-            _currentPosition = default;
-            _nextPosition = default;
-            _sequence = default;
-            HasValueSequence = false;
-            ValueSequence = ReadOnlySequence<byte>.Empty;
-        }
-
-        /// <summary>
-        /// Read the next JSON token from input source.
-        /// </summary>
-        /// <returns>True if the token was read successfully, else false.</returns>
-        /// <exception cref="JsonReaderException">
-        /// Thrown when an invalid JSON token is encountered according to the JSON RFC
-        /// or if the current depth exceeds the recursive limit set by the max depth.
-        /// </exception>
-        public bool Read()
-        {
-            return _isSingleSegment ? ReadSingleSegment() : ReadMultiSegment();
-        }
-
-        private void StartObject()
-        {
-            if (CurrentDepth >= _maxDepth)
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ObjectDepthTooLarge);
-
-            _bitStack.PushTrue();
-
-            _consumed++;
-            _bytePositionInLine++;
-            _tokenType = JsonTokenType.StartObject;
-            _inObject = true;
-        }
-
-        private void EndObject()
-        {
-            if (!_inObject || CurrentDepth <= 0)
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBrace);
-
-            _tokenType = JsonTokenType.EndObject;
-
-            UpdateBitStackOnEndToken();
-        }
-
-        private void StartArray()
-        {
-            if (CurrentDepth >= _maxDepth)
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ArrayDepthTooLarge);
-
-            _bitStack.PushFalse();
-
-            _consumed++;
-            _bytePositionInLine++;
-            _tokenType = JsonTokenType.StartArray;
-            _inObject = false;
-        }
-
-        private void EndArray()
-        {
-            if (_inObject || CurrentDepth <= 0)
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBracket);
-
-            _tokenType = JsonTokenType.EndArray;
-
-            UpdateBitStackOnEndToken();
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void UpdateBitStackOnEndToken()
-        {
-            _consumed++;
-            _bytePositionInLine++;
-            _inObject = _bitStack.Pop();
-        }
-
-        private bool ReadSingleSegment()
-        {
-            bool retVal = false;
-
-            if (!HasMoreData())
-            {
-                goto Done;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            // SkipWhiteSpace only skips the whitespace characters as defined by JSON RFC 8259 section 2.
-            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
-            // Those cases are captured in ConsumeNextToken and ConsumeValue.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpace();
-                if (!HasMoreData())
-                {
-                    goto Done;
-                }
-                first = _buffer[_consumed];
-            }
-
-            if (_tokenType == JsonTokenType.None)
-            {
-                goto ReadFirstToken;
-            }
-
-            if (first == JsonConstants.Slash)
-            {
-                retVal = ConsumeNextTokenOrRollback(first);
-                goto Done;
-            }
-
-            if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (first == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    retVal = ConsumePropertyName();
-                    if (!retVal)
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (first == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    retVal = ConsumeValue(first);
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                retVal = ConsumeValue(first);
-                goto Done;
-            }
-            else
-            {
-                retVal = ConsumeNextTokenOrRollback(first);
-                goto Done;
-            }
-
-            retVal = true;
-
-        Done:
-            return retVal;
-
-        ReadFirstToken:
-            retVal = ReadFirstToken(first);
-            goto Done;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private bool HasMoreData()
-        {
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (_isNotPrimitive && IsLastSpan)
-                {
-                    if (CurrentDepth != 0)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ZeroDepthAtEnd);
-                    }
-
-                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && _tokenType == JsonTokenType.Comment)
-                    {
-                        return false;
-                    }
-
-                    if (_tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
-                    }
-                }
-                return false;
-            }
-            return true;
-        }
-
-        // Unlike the parameter-less overload of HasMoreData, if there is no more data when this method is called, we know the JSON input is invalid.
-        // This is because, this method is only called after a ',' (i.e. we expect a value/property name) or after 
-        // a property name, which means it must be followed by a value.
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private bool HasMoreData(ExceptionResource resource)
-        {
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, resource);
-                }
-                return false;
-            }
-            return true;
-        }
-
-        private bool ReadFirstToken(byte first)
-        {
-            if (first == JsonConstants.OpenBrace)
-            {
-                _bitStack.SetFirstBit();
-                _tokenType = JsonTokenType.StartObject;
-                _consumed++;
-                _bytePositionInLine++;
-                _inObject = true;
-                _isNotPrimitive = true;
-            }
-            else if (first == JsonConstants.OpenBracket)
-            {
-                _bitStack.ResetFirstBit();
-                _tokenType = JsonTokenType.StartArray;
-                _consumed++;
-                _bytePositionInLine++;
-                _isNotPrimitive = true;
-            }
-            else
-            {
-                // Create local copy to avoid bounds checks.
-                ReadOnlySpan<byte> localBuffer = _buffer;
-
-                if (JsonReaderHelper.IsDigit(first) || first == '-')
-                {
-                    if (!TryGetNumber(localBuffer.Slice(_consumed), out int numberOfBytes))
-                    {
-                        return false;
-                    }
-                    _tokenType = JsonTokenType.Number;
-                    _consumed += numberOfBytes;
-                    _bytePositionInLine += numberOfBytes;
-                }
-                else if (!ConsumeValue(first))
-                {
-                    return false;
-                }
-
-                // Cannot use HasMoreData since the JSON payload contains a single, non-primitive value
-                // and hence must be handled differently.
-                if (_consumed >= (uint)localBuffer.Length)
-                {
-                    goto SetIsNotPrimitiveAndReturnTrue;
-                }
-
-                if (localBuffer[_consumed] <= JsonConstants.Space)
-                {
-                    SkipWhiteSpace();
-                    if (_consumed >= (uint)localBuffer.Length)
-                    {
-                        goto SetIsNotPrimitiveAndReturnTrue;
-                    }
-                }
-
-                if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
-                {
-                    if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
-                    {
-                        // This is necessary to avoid throwing when the user has 1 or more comments as the first token
-                        // OR if there is a comment after a single, non-primitive value.
-                        // In this mode, ConsumeValue consumes the comment and we need to return it as a token.
-                        // along with future comments in subsequeunt reads.
-                        if (_tokenType == JsonTokenType.Comment || localBuffer[_consumed] == JsonConstants.Slash)
-                        {
-                            return true;
-                        }
-                    }
-                    else
-                    {
-                        Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                        if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
-                        {
-                            _isNotPrimitive = true;
-                        }
-                        goto SetIsNotPrimitiveAndReturnTrue;
-                    }
-                }
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, localBuffer[_consumed]);
-
-            SetIsNotPrimitiveAndReturnTrue:
-                if (_tokenType == JsonTokenType.StartObject || _tokenType == JsonTokenType.StartArray)
-                {
-                    _isNotPrimitive = true;
-                }
-                // Intentionally fall out of the if-block to return true
-            }
-            return true;
-        }
-
-        private void SkipWhiteSpace()
-        {
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer;
-            for (; _consumed < localBuffer.Length; _consumed++)
-            {
-                byte val = localBuffer[_consumed];
-
-                // JSON RFC 8259 section 2 says only these 4 characters count, not all of the Unicode defintions of whitespace.
-                if (val != JsonConstants.Space &&
-                    val != JsonConstants.CarriageReturn &&
-                    val != JsonConstants.LineFeed &&
-                    val != JsonConstants.Tab)
-                {
-                    break;
-                }
-
-                if (val == JsonConstants.LineFeed)
-                {
-                    _lineNumber++;
-                    _bytePositionInLine = 0;
-                }
-                else
-                {
-                    _bytePositionInLine++;
-                }
-            }
-        }
-
-        /// <summary>
-        /// This method contains the logic for processing the next value token and determining
-        /// what type of data it is.
-        /// </summary>
-        private bool ConsumeValue(byte marker)
-        {
-            while (true)
-            {
-                if (marker == JsonConstants.Quote)
-                {
-                    return ConsumeString();
-                }
-                else if (marker == JsonConstants.OpenBrace)
-                {
-                    StartObject();
-                }
-                else if (marker == JsonConstants.OpenBracket)
-                {
-                    StartArray();
-                }
-                else if (JsonReaderHelper.IsDigit(marker) || marker == '-')
-                {
-                    return ConsumeNumber();
-                }
-                else if (marker == 'f')
-                {
-                    return ConsumeLiteral(JsonConstants.FalseValue, JsonTokenType.False);
-                }
-                else if (marker == 't')
-                {
-                    return ConsumeLiteral(JsonConstants.TrueValue, JsonTokenType.True);
-                }
-                else if (marker == 'n')
-                {
-                    return ConsumeLiteral(JsonConstants.NullValue, JsonTokenType.Null);
-                }
-                else
-                {
-                    switch (_readerOptions.CommentHandling)
-                    {
-                        case JsonCommentHandling.Disallow:
-                            break;
-                        case JsonCommentHandling.Allow:
-                            if (marker == JsonConstants.Slash)
-                            {
-                                return ConsumeComment();
-                            }
-                            break;
-                        default:
-                            Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                            if (marker == JsonConstants.Slash)
-                            {
-                                if (SkipComment())
-                                {
-                                    if (_consumed >= (uint)_buffer.Length)
-                                    {
-                                        if (_isNotPrimitive && IsLastSpan && _tokenType != JsonTokenType.EndArray && _tokenType != JsonTokenType.EndObject)
-                                        {
-                                            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidEndOfJsonNonPrimitive);
-                                        }
-                                        return false;
-                                    }
-
-                                    marker = _buffer[_consumed];
-
-                                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                                    if (marker <= JsonConstants.Space)
-                                    {
-                                        SkipWhiteSpace();
-                                        if (!HasMoreData())
-                                        {
-                                            return false;
-                                        }
-                                        marker = _buffer[_consumed];
-                                    }
-
-                                    // Skip comments and consume the actual JSON value.
-                                    continue;
-                                }
-                                return false;
-                            }
-                            break;
-                    }
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-                }
-                break;
-            }
-            return true;
-        }
-
-        // Consumes 'null', or 'true', or 'false'
-        private bool ConsumeLiteral(ReadOnlySpan<byte> literal, JsonTokenType tokenType)
-        {
-            ReadOnlySpan<byte> span = _buffer.Slice(_consumed);
-            Debug.Assert(span.Length > 0);
-            Debug.Assert(span[0] == 'n' || span[0] == 't' || span[0] == 'f');
-
-            if (!span.StartsWith(literal))
-            {
-                return CheckLiteral(span, literal);
-            }
-
-            ValueSpan = span.Slice(0, literal.Length);
-            _tokenType = tokenType;
-            _consumed += literal.Length;
-            _bytePositionInLine += literal.Length;
-            return true;
-        }
-
-        private bool CheckLiteral(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal)
-        {
-            Debug.Assert(span.Length > 0 && span[0] == literal[0]);
-
-            int indexOfFirstMismatch = 0;
-
-            for (int i = 1; i < literal.Length; i++)
-            {
-                if (span.Length > i)
-                {
-                    if (span[i] != literal[i])
-                    {
-                        _bytePositionInLine += i;
-                        ThrowInvalidLiteral(span);
-                    }
-                }
-                else
-                {
-                    indexOfFirstMismatch = i;
-                    break;
-                }
-            }
-
-            Debug.Assert(indexOfFirstMismatch > 0 && indexOfFirstMismatch < literal.Length);
-
-            if (IsLastSpan)
-            {
-                _bytePositionInLine += indexOfFirstMismatch;
-                ThrowInvalidLiteral(span);
-            }
-            return false;
-        }
-
-        private void ThrowInvalidLiteral(ReadOnlySpan<byte> span)
-        {
-            byte firstByte = span[0];
-
-            ExceptionResource resource;
-            switch (firstByte)
-            {
-                case (byte)'t':
-                    resource = ExceptionResource.ExpectedTrue;
-                    break;
-                case (byte)'f':
-                    resource = ExceptionResource.ExpectedFalse;
-                    break;
-                default:
-                    Debug.Assert(firstByte == 'n');
-                    resource = ExceptionResource.ExpectedNull;
-                    break;
-            }
-            ThrowHelper.ThrowJsonReaderException(ref this, resource, bytes: span);
-        }
-
-        private bool ConsumeNumber()
-        {
-            if (!TryGetNumber(_buffer.Slice(_consumed), out int consumed))
-            {
-                return false;
-            }
-
-            _tokenType = JsonTokenType.Number;
-            _consumed += consumed;
-            _bytePositionInLine += consumed;
-
-            if (_consumed >= (uint)_buffer.Length)
-            {
-                if (!_isNotPrimitive)
-                {
-                    return true;
-                }
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed - 1]);
-                }
-                return false;
-            }
-
-            // TODO: https://github.com/dotnet/corefx/issues/33294
-            if (JsonConstants.Delimiters.IndexOf(_buffer[_consumed]) < 0)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, _buffer[_consumed]);
-            }
-            return true;
-        }
-
-        private bool ConsumePropertyName()
-        {
-            if (!ConsumeString())
-            {
-                return false;
-            }
-
-            if (!HasMoreData(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
-            {
-                return false;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            // We do not validate if 'first' is an invalid JSON byte here (such as control characters).
-            // Those cases are captured below where we only accept ':'.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpace();
-                if (!HasMoreData(ExceptionResource.ExpectedValueAfterPropertyNameNotFound))
-                {
-                    return false;
-                }
-                first = _buffer[_consumed];
-            }
-
-            // The next character must be a key / value seperator. Validate and skip.
-            if (first != JsonConstants.KeyValueSeperator)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedSeparatorAfterPropertyNameNotFound, first);
-            }
-
-            _consumed++;
-            _bytePositionInLine++;
-            _tokenType = JsonTokenType.PropertyName;
-            return true;
-        }
-
-        private bool ConsumeString()
-        {
-            Debug.Assert(_buffer.Length >= _consumed + 1);
-            Debug.Assert(_buffer[_consumed] == JsonConstants.Quote);
-
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-
-            // Vectorized search for either quote, backslash, or any control character.
-            // If the first found byte is a quote, we have reached an end of string, and
-            // can avoid validation.
-            // Otherwise, in the uncommon case, iterate one character at a time and validate.
-            int idx = localBuffer.IndexOfQuoteOrAnyControlOrBackSlash();
-
-            if (idx >= 0)
-            {
-                byte foundByte = localBuffer[idx];
-                if (foundByte == JsonConstants.Quote)
-                {
-                    _bytePositionInLine += idx + 2; // Add 2 for the start and end quotes.
-                    ValueSpan = localBuffer.Slice(0, idx);
-                    _tokenType = JsonTokenType.String;
-                    _consumed += idx + 2;
-                    return true;
-                }
-                else
-                {
-                    return ConsumeStringAndValidate(localBuffer, idx);
-                }
-            }
-            else
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                }
-                return false;
-            }
-        }
-
-        // Found a backslash or control characters which are considered invalid within a string.
-        // Search through the rest of the string one byte at a time.
-        // https://tools.ietf.org/html/rfc8259#section-7
-        private bool ConsumeStringAndValidate(ReadOnlySpan<byte> data, int idx)
-        {
-            Debug.Assert(idx >= 0 && idx < data.Length);
-            Debug.Assert(data[idx] != JsonConstants.Quote);
-            Debug.Assert(data[idx] == JsonConstants.BackSlash || data[idx] < JsonConstants.Space);
-
-            long prevLineBytePosition = _bytePositionInLine;
-            long prevLineNumber = _lineNumber;
-
-            _bytePositionInLine += idx + 1; // Add 1 for the first quote
-
-            bool nextCharEscaped = false;
-            for (; idx < data.Length; idx++)
-            {
-                byte currentByte = data[idx];
-                if (currentByte == JsonConstants.Quote)
-                {
-                    if (!nextCharEscaped)
-                    {
-                        goto Done;
-                    }
-                    nextCharEscaped = false;
-                }
-                else if (currentByte == JsonConstants.BackSlash)
-                {
-                    nextCharEscaped = !nextCharEscaped;
-                }
-                else if (nextCharEscaped)
-                {
-                    int index = JsonConstants.EscapableChars.IndexOf(currentByte);
-                    if (index == -1)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterAfterEscapeWithinString, currentByte);
-                    }
-
-                    if (currentByte == JsonConstants.Quote)
-                    {
-                        // Ignore an escaped quote.
-                        // This is likely the most common case, so adding an explicit check
-                        // to avoid doing the unnecessary checks below.
-                    }
-                    else if (currentByte == 'n')
-                    {
-                        // Escaped new line character
-                        _bytePositionInLine = -1; // Should be 0, but we increment _bytePositionInLine below already
-                        _lineNumber++;
-                    }
-                    else if (currentByte == 'u')
-                    {
-                        // Expecting 4 hex digits to follow the escaped 'u'
-                        _bytePositionInLine++;  // move past the 'u'
-                        if (ValidateHexDigits(data, idx + 1))
-                        {
-                            idx += 4;   // Skip the 4 hex digits, the for loop accounts for idx incrementing past the 'u'
-                        }
-                        else
-                        {
-                            // We found less than 4 hex digits. Check if there is more data to follow, otherwise throw.
-                            idx = data.Length;
-                            break;
-                        }
-
-                    }
-                    nextCharEscaped = false;
-                }
-                else if (currentByte < JsonConstants.Space)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidCharacterWithinString, currentByte);
-                }
-
-                _bytePositionInLine++;
-            }
-
-            if (idx >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfStringNotFound);
-                }
-                _lineNumber = prevLineNumber;
-                _bytePositionInLine = prevLineBytePosition;
-                return false;
-            }
-
-        Done:
-            _bytePositionInLine++;  // Add 1 for the end quote
-            ValueSpan = data.Slice(0, idx);
-            _tokenType = JsonTokenType.String;
-            _consumed += idx + 2;
-            return true;
-        }
-
-        private bool ValidateHexDigits(ReadOnlySpan<byte> data, int idx)
-        {
-            for (int j = idx; j < data.Length; j++)
-            {
-                byte nextByte = data[j];
-                if (!JsonReaderHelper.IsHexDigit(nextByte))
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.InvalidHexCharacterWithinString, nextByte);
-                }
-                if (j - idx >= 3)
-                {
-                    return true;
-                }
-                _bytePositionInLine++;
-            }
-
-            return false;
-        }
-
-        // https://tools.ietf.org/html/rfc7159#section-6
-        private bool TryGetNumber(ReadOnlySpan<byte> data, out int consumed)
-        {
-            // TODO: https://github.com/dotnet/corefx/issues/33294
-            Debug.Assert(data.Length > 0);
-
-            _numberFormat = default;
-            consumed = 0;
-            int i = 0;
-
-            ConsumeNumberResult signResult = ConsumeNegativeSign(ref data, ref i);
-            if (signResult == ConsumeNumberResult.NeedMoreData)
-            {
-                return false;
-            }
-
-            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
-
-            byte nextByte = data[i];
-            Debug.Assert(nextByte >= '0' && nextByte <= '9');
-
-            if (nextByte == '0')
-            {
-                ConsumeNumberResult result = ConsumeZero(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-            }
-            else
-            {
-                i++;
-                ConsumeNumberResult result = ConsumeIntegerDigits(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-                if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
-                {
-                    _bytePositionInLine += i;
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
-                }
-            }
-
-            Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');
-
-            if (nextByte == '.')
-            {
-                i++;
-                ConsumeNumberResult result = ConsumeDecimalDigits(ref data, ref i);
-                if (result == ConsumeNumberResult.NeedMoreData)
-                {
-                    return false;
-                }
-                if (result == ConsumeNumberResult.Success)
-                {
-                    goto Done;
-                }
-
-                Debug.Assert(result == ConsumeNumberResult.OperationIncomplete);
-                nextByte = data[i];
-                if (nextByte != 'E' && nextByte != 'e')
-                {
-                    _bytePositionInLine += i;
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
-                }
-            }
-
-            Debug.Assert(nextByte == 'E' || nextByte == 'e');
-            i++;
-            _numberFormat = 'e';
-
-            signResult = ConsumeSign(ref data, ref i);
-            if (signResult == ConsumeNumberResult.NeedMoreData)
-            {
-                return false;
-            }
-
-            Debug.Assert(signResult == ConsumeNumberResult.OperationIncomplete);
-
-            i++;
-            ConsumeNumberResult resultExponent = ConsumeIntegerDigits(ref data, ref i);
-            if (resultExponent == ConsumeNumberResult.NeedMoreData)
-            {
-                return false;
-            }
-            if (resultExponent == ConsumeNumberResult.Success)
-            {
-                goto Done;
-            }
-
-            Debug.Assert(resultExponent == ConsumeNumberResult.OperationIncomplete);
-
-            _bytePositionInLine += i;
-            ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
-
-        Done:
-            ValueSpan = data.Slice(0, i);
-            consumed = i;
-            return true;
-        }
-
-        private ConsumeNumberResult ConsumeNegativeSign(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            byte nextByte = data[i];
-
-            if (nextByte == '-')
-            {
-                i++;
-                if (i >= data.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _bytePositionInLine += i;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-
-                nextByte = data[i];
-                if (!JsonReaderHelper.IsDigit(nextByte))
-                {
-                    _bytePositionInLine += i;
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
-                }
-            }
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeZero(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            Debug.Assert(data[i] == (byte)'0');
-            i++;
-            byte nextByte = default;
-            if (i < data.Length)
-            {
-                nextByte = data[i];
-                if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
-                {
-                    return ConsumeNumberResult.Success;
-                }
-            }
-            else
-            {
-                if (IsLastSpan)
-                {
-                    // A payload containing a single value: "0" is valid
-                    // If we are v with multi-value JSON,
-                    // ConsumeNumber will validate that we have a delimiter following the "0".
-                    return ConsumeNumberResult.Success;
-                }
-                else
-                {
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-            }
-            nextByte = data[i];
-            if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
-            {
-                _bytePositionInLine += i;
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeIntegerDigits(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            byte nextByte = default;
-            for (; i < data.Length; i++)
-            {
-                nextByte = data[i];
-                if (!JsonReaderHelper.IsDigit(nextByte))
-                {
-                    break;
-                }
-            }
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    // A payload containing a single value of integers (e.g. "12") is valid
-                    // If we are dealing with multi-value JSON,
-                    // ConsumeNumber will validate that we have a delimiter following the integer.
-                    return ConsumeNumberResult.Success;
-                }
-                else
-                {
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-            }
-            if (JsonConstants.Delimiters.IndexOf(nextByte) >= 0)
-            {
-                return ConsumeNumberResult.Success;
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private ConsumeNumberResult ConsumeDecimalDigits(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    _bytePositionInLine += i;
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                }
-                return ConsumeNumberResult.NeedMoreData;
-            }
-            byte nextByte = data[i];
-            if (!JsonReaderHelper.IsDigit(nextByte))
-            {
-                _bytePositionInLine += i;
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterDecimal, nextByte);
-            }
-            i++;
-
-            return ConsumeIntegerDigits(ref data, ref i);
-        }
-
-        private ConsumeNumberResult ConsumeSign(ref ReadOnlySpan<byte> data, ref int i)
-        {
-            if (i >= data.Length)
-            {
-                if (IsLastSpan)
-                {
-                    _bytePositionInLine += i;
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                }
-                return ConsumeNumberResult.NeedMoreData;
-            }
-
-            byte nextByte = data[i];
-            if (nextByte == '+' || nextByte == '-')
-            {
-                i++;
-                if (i >= data.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _bytePositionInLine += i;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundEndOfData);
-                    }
-                    return ConsumeNumberResult.NeedMoreData;
-                }
-                nextByte = data[i];
-            }
-
-            if (!JsonReaderHelper.IsDigit(nextByte))
-            {
-                _bytePositionInLine += i;
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.RequiredDigitNotFoundAfterSign, nextByte);
-            }
-
-            return ConsumeNumberResult.OperationIncomplete;
-        }
-
-        private bool ConsumeNextTokenOrRollback(byte marker)
-        {
-            int prevConsumed = _consumed;
-            long prevPosition = _bytePositionInLine;
-            long prevLineNumber = _lineNumber;
-            JsonTokenType prevTokenType = _tokenType;
-            ConsumeTokenResult result = ConsumeNextToken(marker);
-            if (result == ConsumeTokenResult.Success)
-            {
-                return true;
-            }
-            if (result == ConsumeTokenResult.NotEnoughDataRollBackState)
-            {
-                _consumed = prevConsumed;
-                _tokenType = prevTokenType;
-                _bytePositionInLine = prevPosition;
-                _lineNumber = prevLineNumber;
-            }
-            return false;
-        }
-
-        /// <summary>
-        /// This method consumes the next token regardless of whether we are inside an object or an array.
-        /// For an object, it reads the next property name token. For an array, it just reads the next value.
-        /// </summary>
-        private ConsumeTokenResult ConsumeNextToken(byte marker)
-        {
-            if (_readerOptions.CommentHandling != JsonCommentHandling.Disallow)
-            {
-                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow)
-                {
-                    if (marker == JsonConstants.Slash)
-                    {
-                        return ConsumeComment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    if (_tokenType == JsonTokenType.Comment)
-                    {
-                        return ConsumeNextTokenFromLastNonCommentToken();
-                    }
-                }
-                else
-                {
-                    Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Skip);
-                    return ConsumeNextTokenUntilAfterAllCommentsAreSkipped(marker);
-                }
-            }
-
-            if (!_isNotPrimitive)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
-            }
-
-            if (marker == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    return ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                byte first = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (first <= JsonConstants.Space)
-                {
-                    SkipWhiteSpace();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    first = _buffer[_consumed];
-                }
-
-                if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash)
-                {
-                    return ConsumeComment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-
-                if (_inObject)
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-                    return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                else
-                {
-                    return ConsumeValue(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-            }
-            else if (marker == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (marker == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
-            }
-            return ConsumeTokenResult.Success;
-        }
-
-        private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken()
-        {
-            if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType))
-            {
-                _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray;
-            }
-            else
-            {
-                _tokenType = _previousTokenType;
-            }
-
-            Debug.Assert(_tokenType != JsonTokenType.Comment);
-
-            if (!HasMoreData())
-            {
-                goto RollBack;
-            }
-
-            byte first = _buffer[_consumed];
-
-            // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-            if (first <= JsonConstants.Space)
-            {
-                SkipWhiteSpace();
-                if (!HasMoreData())
-                {
-                    goto RollBack;
-                }
-                first = _buffer[_consumed];
-            }
-
-            if (!_isNotPrimitive && _tokenType != JsonTokenType.None)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, first);
-            }
-
-            Debug.Assert(first != JsonConstants.Slash);
-
-            if (first == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    goto RollBack;
-                }
-                first = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (first <= JsonConstants.Space)
-                {
-                    SkipWhiteSpace();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        goto RollBack;
-                    }
-                    first = _buffer[_consumed];
-                }
-
-                if (_inObject)
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-                    if (ConsumePropertyName())
-                    {
-                        goto Done;
-                    }
-                    else
-                    {
-                        goto RollBack;
-                    }
-                }
-                else
-                {
-                    if (ConsumeValue(first))
-                    {
-                        goto Done;
-                    }
-                    else
-                    {
-                        goto RollBack;
-                    }
-                }
-            }
-            else if (first == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (first == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else if (_tokenType == JsonTokenType.None)
-            {
-                if (ReadFirstToken(first))
-                {
-                    goto Done;
-                }
-                else
-                {
-                    goto RollBack;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (first == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (first != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first);
-                    }
-
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    if (!ConsumePropertyName())
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                        goto RollBack;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (first == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    if (!ConsumeValue(first))
-                    {
-                        goto RollBack;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                if (!ConsumeValue(first))
-                {
-                    goto RollBack;
-                }
-                goto Done;
-            }
-            else
-            {
-                goto RollBack;
-            }
-
-        Done:
-            return ConsumeTokenResult.Success;
-
-        RollBack:
-            return ConsumeTokenResult.NotEnoughDataRollBackState;
-        }
-
-        private bool SkipAllComments(ref byte marker)
-        {
-            while (marker == JsonConstants.Slash)
-            {
-                if (SkipComment())
-                {
-                    if (!HasMoreData())
-                    {
-                        goto IncompleteNoRollback;
-                    }
-
-                    marker = _buffer[_consumed];
-
-                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                    if (marker <= JsonConstants.Space)
-                    {
-                        SkipWhiteSpace();
-                        if (!HasMoreData())
-                        {
-                            goto IncompleteNoRollback;
-                        }
-                        marker = _buffer[_consumed];
-                    }
-                }
-                else
-                {
-                    goto IncompleteNoRollback;
-                }
-            }
-            return true;
-
-        IncompleteNoRollback:
-            return false;
-        }
-
-        private bool SkipAllComments(ref byte marker, ExceptionResource resource)
-        {
-            while (marker == JsonConstants.Slash)
-            {
-                if (SkipComment())
-                {
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreData(resource))
-                    {
-                        goto IncompleteRollback;
-                    }
-
-                    marker = _buffer[_consumed];
-
-                    // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                    if (marker <= JsonConstants.Space)
-                    {
-                        SkipWhiteSpace();
-                        // The next character must be a start of a property name or value.
-                        if (!HasMoreData(resource))
-                        {
-                            goto IncompleteRollback;
-                        }
-                        marker = _buffer[_consumed];
-                    }
-                }
-                else
-                {
-                    goto IncompleteRollback;
-                }
-            }
-            return true;
-
-        IncompleteRollback:
-            return false;
-        }
-
-        private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkipped(byte marker)
-        {
-            if (!SkipAllComments(ref marker))
-            {
-                goto IncompleteNoRollback;
-            }
-
-            if (_tokenType == JsonTokenType.StartObject)
-            {
-                if (marker == JsonConstants.CloseBrace)
-                {
-                    EndObject();
-                }
-                else
-                {
-                    if (marker != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
-                    }
-
-                    int prevConsumed = _consumed;
-                    long prevPosition = _bytePositionInLine;
-                    long prevLineNumber = _lineNumber;
-                    if (!ConsumePropertyName())
-                    {
-                        // roll back potential changes
-                        _consumed = prevConsumed;
-                        _tokenType = JsonTokenType.StartObject;
-                        _bytePositionInLine = prevPosition;
-                        _lineNumber = prevLineNumber;
-                        goto IncompleteNoRollback;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.StartArray)
-            {
-                if (marker == JsonConstants.CloseBracket)
-                {
-                    EndArray();
-                }
-                else
-                {
-                    if (!ConsumeValue(marker))
-                    {
-                        goto IncompleteNoRollback;
-                    }
-                    goto Done;
-                }
-            }
-            else if (_tokenType == JsonTokenType.PropertyName)
-            {
-                if (!ConsumeValue(marker))
-                {
-                    goto IncompleteNoRollback;
-                }
-                goto Done;
-            }
-            else if (!_isNotPrimitive)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
-            }
-            else if (marker == JsonConstants.ListSeperator)
-            {
-                _consumed++;
-                _bytePositionInLine++;
-
-                if (_consumed >= (uint)_buffer.Length)
-                {
-                    if (IsLastSpan)
-                    {
-                        _consumed--;
-                        _bytePositionInLine--;
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound);
-                    }
-                    return ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                marker = _buffer[_consumed];
-
-                // This check is done as an optimization to avoid calling SkipWhiteSpace when not necessary.
-                if (marker <= JsonConstants.Space)
-                {
-                    SkipWhiteSpace();
-                    // The next character must be a start of a property name or value.
-                    if (!HasMoreData(ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                    {
-                        return ConsumeTokenResult.NotEnoughDataRollBackState;
-                    }
-                    marker = _buffer[_consumed];
-                }
-
-                if (!SkipAllComments(ref marker, ExceptionResource.ExpectedStartOfPropertyOrValueNotFound))
-                {
-                    goto IncompleteRollback;
-                }
-
-                if (_inObject)
-                {
-                    if (marker != JsonConstants.Quote)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker);
-                    }
-                    return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-                else
-                {
-                    return ConsumeValue(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState;
-                }
-            }
-            else if (marker == JsonConstants.CloseBrace)
-            {
-                EndObject();
-            }
-            else if (marker == JsonConstants.CloseBracket)
-            {
-                EndArray();
-            }
-            else
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.FoundInvalidCharacter, marker);
-            }
-
-        Done:
-            return ConsumeTokenResult.Success;
-        IncompleteNoRollback:
-            return ConsumeTokenResult.IncompleteNoRollBackNecessary;
-        IncompleteRollback:
-            return ConsumeTokenResult.NotEnoughDataRollBackState;
-        }
-
-        private bool SkipComment()
-        {
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-
-            if (localBuffer.Length > 0)
-            {
-                byte marker = localBuffer[0];
-                if (marker == JsonConstants.Slash)
-                {
-                    return SkipSingleLineComment(localBuffer.Slice(1), out _);
-                }
-                else if (marker == JsonConstants.Asterisk)
-                {
-                    return SkipMultiLineComment(localBuffer.Slice(1), out _);
-                }
-                else
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-                }
-            }
-
-            if (IsLastSpan)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-            }
-            return false;
-        }
-
-        private bool SkipSingleLineComment(ReadOnlySpan<byte> localBuffer, out int idx)
-        {
-            // TODO: https://github.com/dotnet/corefx/issues/33293
-            idx = localBuffer.IndexOf(JsonConstants.LineFeed);
-            if (idx == -1)
-            {
-                if (IsLastSpan)
-                {
-                    idx = localBuffer.Length;
-                    // Assume everything on this line is a comment and there is no more data.
-                    _bytePositionInLine += 2 + localBuffer.Length;
-                    goto Done;
-                }
-                return false;
-            }
-
-            idx++;
-            _bytePositionInLine = 0;
-            _lineNumber++;
-        Done:
-            _consumed += 2 + idx;
-            return true;
-        }
-
-        private bool SkipMultiLineComment(ReadOnlySpan<byte> localBuffer, out int idx)
-        {
-            idx = 0;
-            while (true)
-            {
-                int foundIdx = localBuffer.Slice(idx).IndexOf(JsonConstants.Slash);
-                if (foundIdx == -1)
-                {
-                    if (IsLastSpan)
-                    {
-                        ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.EndOfCommentNotFound);
-                    }
-                    return false;
-                }
-                if (foundIdx != 0 && localBuffer[foundIdx + idx - 1] == JsonConstants.Asterisk)
-                {
-                    idx += foundIdx;
-                    break;
-                }
-                idx += foundIdx + 1;
-            }
-
-            Debug.Assert(idx >= 1);
-
-            // Consume the /* and */ characters that are part of the multi-line comment.
-            // Since idx is pointing at right after the final '*' (i.e. before the last '/'), we don't need to count that character.
-            // Hence, we increment consumed by 3 (instead of 4).
-            _consumed += 4 + idx - 1;
-
-            (int newLines, int newLineIndex) = JsonReaderHelper.CountNewLines(localBuffer.Slice(0, idx - 1));
-            _lineNumber += newLines;
-            if (newLineIndex != -1)
-            {
-                _bytePositionInLine = idx - newLineIndex;
-            }
-            else
-            {
-                _bytePositionInLine += 4 + idx - 1;
-            }
-            return true;
-        }
-
-        private bool ConsumeComment()
-        {
-            // Create local copy to avoid bounds checks.
-            ReadOnlySpan<byte> localBuffer = _buffer.Slice(_consumed + 1);
-
-            if (localBuffer.Length > 0)
-            {
-                byte marker = localBuffer[0];
-                if (marker == JsonConstants.Slash)
-                {
-                    return ConsumeSingleLineComment(localBuffer.Slice(1), _consumed);
-                }
-                else if (marker == JsonConstants.Asterisk)
-                {
-                    return ConsumeMultiLineComment(localBuffer.Slice(1), _consumed);
-                }
-                else
-                {
-                    ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, marker);
-                }
-            }
-
-            if (IsLastSpan)
-            {
-                ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfValueNotFound, JsonConstants.Slash);
-            }
-            return false;
-        }
-
-        private bool ConsumeSingleLineComment(ReadOnlySpan<byte> localBuffer, int previousConsumed)
-        {
-            if (!SkipSingleLineComment(localBuffer, out int idx))
-            {
-                return false;
-            }
-
-            ValueSpan = _buffer.Slice(previousConsumed, idx + 2);   // Include the double slash and potential line feed at the end of the comment as part of it.
-            if (_tokenType != JsonTokenType.Comment)
-            {
-                _previousTokenType = _tokenType;
-            }
-            _tokenType = JsonTokenType.Comment;
-            return true;
-        }
-
-        private bool ConsumeMultiLineComment(ReadOnlySpan<byte> localBuffer, int previousConsumed)
-        {
-            if (!SkipMultiLineComment(localBuffer, out int idx))
-            {
-                return false;
-            }
-
-            ValueSpan = _buffer.Slice(previousConsumed, idx + 3); // Include the slash/asterisk and final slash at the end of the comment as part of it.
-            if (_tokenType != JsonTokenType.Comment)
-            {
-                _previousTokenType = _tokenType;
-            }
-            _tokenType = JsonTokenType.Comment;
-            return true;
-        }
-    }
-}
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 c4391473a191be1b292bfaf54f5fd97a3b6dc74a..41cff4a489648344e634cde4b762e378e9c70bef 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 3a181eddcf7439f788137e421081db908a97212c..7ca71749e3f695fe46c20721ebce44d629b87836 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());
+        }
+    }
+}