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;
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) { }
+ }
}
<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>
</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" />
<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>
+++ /dev/null
-// 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,
- }
-}
+++ /dev/null
-// 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,
- }
-}
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)'\\';
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;
}
}
+++ /dev/null
-// 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; }
- }
-}
+++ /dev/null
-// 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;
- }
-}
+++ /dev/null
-// 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; }
- }
-}
+++ /dev/null
-// 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<byte>. If the <see cref="Utf8JsonReader"/> was constructed
- /// with a ReadOnlySpan<byte> 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 (<= 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;
- }
-}
--- /dev/null
+// 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,
+ }
+}
--- /dev/null
+// 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,
+ }
+}
--- /dev/null
+// 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; }
+ }
+}
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+// 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; }
+ }
+}
--- /dev/null
+// 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<byte>. If the <see cref="Utf8JsonReader"/> was constructed
+ /// with a ReadOnlySpan<byte> 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 (<= 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;
+ }
+}
--- /dev/null
+// 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<byte> 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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<byte> slice
+ /// of the input payload. If the JSON is provided within a ReadOnlySequence<byte>
+ /// 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<byte> this will
+ /// always return false. For input data within a ReadOnlySequence<byte>, 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<byte> slice
+ /// of the input payload. If the JSON is provided within a ReadOnlySequence<byte>
+ /// 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<byte>. If the <see cref="Utf8JsonReader"/> was constructed
+ /// with a ReadOnlySpan<byte> 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<byte> 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;
+ }
+ }
+}
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);
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
MismatchedObjectArray,
ObjectDepthTooLarge,
ZeroDepthAtEnd,
+ DepthTooLarge,
+ CannotStartObjectArrayWithoutProperty,
+ CannotStartObjectArrayAfterPrimitiveOrClose,
+ CannotWriteValueWithinObject,
+ CannotWriteValueAfterPrimitive,
+ FailedToGetMinimumSizeSpan,
+ FailedToGetLargerSpan,
+ CannotWritePropertyWithinArray,
}
}
+++ /dev/null
-// 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<byte> 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;
- }
- }
-}
+++ /dev/null
-// 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;
- }
- }
-}
+++ /dev/null
-// 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<byte> slice
- /// of the input payload. If the JSON is provided within a ReadOnlySequence<byte>
- /// 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<byte> this will
- /// always return false. For input data within a ReadOnlySequence<byte>, 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<byte> slice
- /// of the input payload. If the JSON is provided within a ReadOnlySequence<byte>
- /// 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<byte>. If the <see cref="Utf8JsonReader"/> was constructed
- /// with a ReadOnlySpan<byte> 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<byte> 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;
- }
- }
-}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+// 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
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
// 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()
--- /dev/null
+// 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.");
+ }
+ }
+ }
+}
--- /dev/null
+// 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)
+ {
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+}
<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" />
--- /dev/null
+// 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());
+ }
+ }
+}