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>
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;
}
}
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
}
- if (marker == JsonConstants.ListSeperator)
+ if (marker == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
Debug.Assert(first != JsonConstants.Slash);
- if (first == JsonConstants.ListSeperator)
+ if (first == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
{
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
}
- else if (marker == JsonConstants.ListSeperator)
+ else if (marker == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
}
- if (marker == JsonConstants.ListSeperator)
+ if (marker == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
Debug.Assert(first != JsonConstants.Slash);
- if (first == JsonConstants.ListSeperator)
+ if (first == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
{
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndAfterSingleJson, marker);
}
- else if (marker == JsonConstants.ListSeperator)
+ else if (marker == JsonConstants.ListSeparator)
{
_consumed++;
_bytePositionInLine++;
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.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());
+ }
+ }
+}