JsonWriterHelper
using System.Buffers;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Unicode;
namespace System.Text.Json
{
internal static class JsonWriterHelper
{
internal delegate T WriteCallback<T> (ReadOnlySpan<byte> serializedValue);
private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O', byte.MaxValue);
private static readonly StandardFormat s_hexStandardFormat = new StandardFormat('X', 4);
private unsafe static ReadOnlySpan<byte> AllowList => new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.EFE627BE173681E4F55F4133AB4C1782E26D1080CB80CDB6BFAAC81416A2714E, 256);
public static void WriteIndentation(Span<byte> buffer, int indent, byte indentByte)
{
if (indent < 8) {
int num = 0;
while (num + 1 < indent) {
buffer[num++] = indentByte;
buffer[num++] = indentByte;
}
if (num < indent)
buffer[num] = indentByte;
} else
buffer.Slice(0, indent).Fill(indentByte);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateNewLine(string value)
{
if (value == null)
ThrowHelper.ThrowArgumentNullException("value");
if (!(value == "\n") && !(value == "\r\n"))
ThrowHelper.ThrowArgumentOutOfRangeException_NewLine("value");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateIndentCharacter(char value)
{
if (value != ' ' && value != '\t')
ThrowHelper.ThrowArgumentOutOfRangeException_IndentCharacter("value");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateIndentSize(int value)
{
if ((value < 0 || value > 127) ? true : false)
ThrowHelper.ThrowArgumentOutOfRangeException_IndentSize("value", 0, 127);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateProperty(ReadOnlySpan<byte> propertyName)
{
if (propertyName.Length > 166666666)
ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateValue(ReadOnlySpan<byte> value)
{
if (value.Length > 166666666)
ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateDouble(double value)
{
if (!JsonHelpers.IsFinite(value))
ThrowHelper.ThrowArgumentException_ValueNotSupported();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateSingle(float value)
{
if (!JsonHelpers.IsFinite(value))
ThrowHelper.ThrowArgumentException_ValueNotSupported();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateProperty(ReadOnlySpan<char> propertyName)
{
if (propertyName.Length > 166666666)
ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateValue(ReadOnlySpan<char> value)
{
if (value.Length > 166666666)
ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
{
if (propertyName.Length > 166666666 || value.Length > 166666666)
ThrowHelper.ThrowArgumentException(propertyName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<char> value)
{
if (propertyName.Length > 166666666 || value.Length > 166666666)
ThrowHelper.ThrowArgumentException(propertyName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> value)
{
if (propertyName.Length > 166666666 || value.Length > 166666666)
ThrowHelper.ThrowArgumentException(propertyName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
{
if (propertyName.Length > 166666666 || value.Length > 166666666)
ThrowHelper.ThrowArgumentException(propertyName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyNameLength(ReadOnlySpan<char> propertyName)
{
if (propertyName.Length > 166666666)
ThrowHelper.ThrowPropertyNameTooLargeArgumentException(propertyName.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePropertyNameLength(ReadOnlySpan<byte> propertyName)
{
if (propertyName.Length > 166666666)
ThrowHelper.ThrowPropertyNameTooLargeArgumentException(propertyName.Length);
}
internal static void ValidateNumber(ReadOnlySpan<byte> utf8FormattedNumber)
{
int i = 0;
if (utf8FormattedNumber[i] == 45) {
i++;
if (utf8FormattedNumber.Length <= i)
throw new ArgumentException(System.SR.RequiredDigitNotFoundEndOfData, "utf8FormattedNumber");
}
if (utf8FormattedNumber[i] != 48) {
for (; i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]); i++) {
}
} else
i++;
if (i != utf8FormattedNumber.Length) {
byte b = utf8FormattedNumber[i];
if (b == 46) {
i++;
if (utf8FormattedNumber.Length <= i)
throw new ArgumentException(System.SR.RequiredDigitNotFoundEndOfData, "utf8FormattedNumber");
for (; i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]); i++) {
}
if (i == utf8FormattedNumber.Length)
return;
b = utf8FormattedNumber[i];
}
if (b != 101 && b != 69)
throw new ArgumentException(System.SR.Format(System.SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(b)), "utf8FormattedNumber");
i++;
if (utf8FormattedNumber.Length <= i)
throw new ArgumentException(System.SR.RequiredDigitNotFoundEndOfData, "utf8FormattedNumber");
b = utf8FormattedNumber[i];
if (b == 43 || b == 45)
i++;
if (utf8FormattedNumber.Length <= i)
throw new ArgumentException(System.SR.RequiredDigitNotFoundEndOfData, "utf8FormattedNumber");
for (; i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]); i++) {
}
if (i != utf8FormattedNumber.Length)
throw new ArgumentException(System.SR.Format(System.SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])), "utf8FormattedNumber");
}
}
public static bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
{
return Utf8.IsValid(bytes);
}
internal static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
{
int charsRead;
return Utf8.FromUtf16(source, destination, out charsRead, out written, false, true);
}
internal unsafe static T WriteString<T>(ReadOnlySpan<byte> utf8Value, WriteCallback<T> writeCallback)
{
int num = NeedsEscaping(utf8Value, JavaScriptEncoder.Default);
if (num != -1) {
int num2 = checked(2 + GetMaxEscapedLength(utf8Value.Length, num));
byte[] array = null;
try {
Span<byte> span;
if (num2 > 256) {
array = ArrayPool<byte>.Shared.Rent(num2);
span = array;
} else
span = new Span<byte>((void*)stackalloc byte[256], 256);
span[0] = 34;
EscapeString(utf8Value, span.Slice(1), num, JavaScriptEncoder.Default, out int written);
span[1 + written] = 34;
return writeCallback(span.Slice(0, written + 2));
} finally {
if (array != null)
ArrayPool<byte>.Shared.Return(array, false);
}
}
int num3 = utf8Value.Length + 2;
byte[] array2 = null;
try {
Span<byte> span2 = (num3 <= 256) ? new Span<byte>((void*)stackalloc byte[256], 256).Slice(0, num3) : (array2 = ArrayPool<byte>.Shared.Rent(num3)).AsSpan(0, num3);
Span<byte> span3 = span2;
span3[0] = 34;
utf8Value.CopyTo(span3.Slice(1));
span3[span3.Length - 1] = 34;
return writeCallback(span3);
} finally {
if (array2 != null)
ArrayPool<byte>.Shared.Return(array2, false);
}
}
public unsafe static void WriteDateTimeTrimmed(Span<byte> buffer, DateTime value, out int bytesWritten)
{
Span<byte> destination = new Span<byte>(stackalloc byte[33], 33);
Utf8Formatter.TryFormat(value, destination, out bytesWritten, s_dateTimeStandardFormat);
TrimDateTimeOffset(destination.Slice(0, bytesWritten), out bytesWritten);
destination.Slice(0, bytesWritten).CopyTo(buffer);
}
public unsafe static void WriteDateTimeOffsetTrimmed(Span<byte> buffer, DateTimeOffset value, out int bytesWritten)
{
Span<byte> destination = new Span<byte>(stackalloc byte[33], 33);
Utf8Formatter.TryFormat(value, destination, out bytesWritten, s_dateTimeStandardFormat);
TrimDateTimeOffset(destination.Slice(0, bytesWritten), out bytesWritten);
destination.Slice(0, bytesWritten).CopyTo(buffer);
}
public static void TrimDateTimeOffset(Span<byte> buffer, out int bytesWritten)
{
if (buffer[26] != 48)
bytesWritten = buffer.Length;
else {
int num = (buffer[25] != 48) ? 26 : ((buffer[24] != 48) ? 25 : ((buffer[23] != 48) ? 24 : ((buffer[22] != 48) ? 23 : ((buffer[21] != 48) ? 22 : ((buffer[20] != 48) ? 21 : 19)))));
if (buffer.Length == 27)
bytesWritten = num;
else if (buffer.Length == 33) {
buffer[num] = buffer[27];
buffer[num + 1] = buffer[28];
buffer[num + 2] = buffer[29];
buffer[num + 3] = buffer[30];
buffer[num + 4] = buffer[31];
buffer[num + 5] = buffer[32];
bytesWritten = num + 6;
} else {
buffer[num] = 90;
bytesWritten = num + 1;
}
}
}
private static bool NeedsEscaping(byte value)
{
return AllowList[value] == 0;
}
private static bool NeedsEscapingNoBoundsCheck(char value)
{
return AllowList[value] == 0;
}
public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder encoder)
{
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value);
}
public unsafe static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder encoder)
{
if (!value.IsEmpty) {
fixed (char* text = &value.GetPinnableReference()) {
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(text, value.Length);
}
}
return -1;
}
public static int GetMaxEscapedLength(int textLength, int firstIndexToEscape)
{
return firstIndexToEscape + 6 * (textLength - firstIndexToEscape);
}
private static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, JavaScriptEncoder encoder, ref int consumed, ref int written, bool isFinalBlock)
{
int bytesConsumed;
int bytesWritten;
switch (encoder.EncodeUtf8(value, destination, out bytesConsumed, out bytesWritten, isFinalBlock)) {
case OperationStatus.NeedMoreData:
if (!isFinalBlock)
break;
goto default;
default:
ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(bytesWritten));
break;
case OperationStatus.Done:
break;
}
written += bytesWritten;
consumed += bytesConsumed;
}
public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
{
EscapeString(value, destination, indexOfFirstByteToEscape, encoder, out int _, out written, true);
}
public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int consumed, out int written, bool isFinalBlock = true)
{
value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
written = indexOfFirstByteToEscape;
consumed = indexOfFirstByteToEscape;
if (encoder != null) {
destination = destination.Slice(indexOfFirstByteToEscape);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, encoder, ref consumed, ref written, isFinalBlock);
} else {
while (true) {
if (indexOfFirstByteToEscape >= value.Length)
return;
byte b = value[indexOfFirstByteToEscape];
if (!IsAsciiValue(b))
break;
if (NeedsEscaping(b)) {
EscapeNextBytes(b, destination, ref written);
indexOfFirstByteToEscape++;
consumed++;
} else {
destination[written] = b;
written++;
indexOfFirstByteToEscape++;
consumed++;
}
}
destination = destination.Slice(written);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, JavaScriptEncoder.Default, ref consumed, ref written, isFinalBlock);
}
}
private static void EscapeNextBytes(byte value, Span<byte> destination, ref int written)
{
destination[written++] = 92;
switch (value) {
case 34:
destination[written++] = 117;
destination[written++] = 48;
destination[written++] = 48;
destination[written++] = 50;
destination[written++] = 50;
break;
case 10:
destination[written++] = 110;
break;
case 13:
destination[written++] = 114;
break;
case 9:
destination[written++] = 116;
break;
case 92:
destination[written++] = 92;
break;
case 8:
destination[written++] = 98;
break;
case 12:
destination[written++] = 102;
break;
default:
destination[written++] = 117;
Utf8Formatter.TryFormat(value, destination.Slice(written), out int bytesWritten, s_hexStandardFormat);
written += bytesWritten;
break;
}
}
private static bool IsAsciiValue(byte value)
{
return value <= 127;
}
private static bool IsAsciiValue(char value)
{
return value <= '';
}
private static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, JavaScriptEncoder encoder, ref int consumed, ref int written, bool isFinalBlock)
{
int charsConsumed;
int charsWritten;
switch (encoder.Encode(value, destination, out charsConsumed, out charsWritten, isFinalBlock)) {
case OperationStatus.NeedMoreData:
if (!isFinalBlock)
break;
goto default;
default:
ThrowHelper.ThrowArgumentException_InvalidUTF16(value[charsWritten]);
break;
case OperationStatus.Done:
break;
}
written += charsWritten;
consumed += charsConsumed;
}
public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
{
EscapeString(value, destination, indexOfFirstByteToEscape, encoder, out int _, out written, true);
}
public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int consumed, out int written, bool isFinalBlock = true)
{
value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
written = indexOfFirstByteToEscape;
consumed = indexOfFirstByteToEscape;
if (encoder != null) {
destination = destination.Slice(indexOfFirstByteToEscape);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, encoder, ref consumed, ref written, isFinalBlock);
} else {
while (true) {
if (indexOfFirstByteToEscape >= value.Length)
return;
char c = value[indexOfFirstByteToEscape];
if (!IsAsciiValue(c))
break;
if (NeedsEscapingNoBoundsCheck(c)) {
EscapeNextChars(c, destination, ref written);
indexOfFirstByteToEscape++;
consumed++;
} else {
destination[written] = c;
written++;
indexOfFirstByteToEscape++;
consumed++;
}
}
destination = destination.Slice(written);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, JavaScriptEncoder.Default, ref consumed, ref written, isFinalBlock);
}
}
private static void EscapeNextChars(char value, Span<char> destination, ref int written)
{
destination[written++] = '\\';
switch ((byte)value) {
case 34:
destination[written++] = 'u';
destination[written++] = '0';
destination[written++] = '0';
destination[written++] = '2';
destination[written++] = '2';
break;
case 10:
destination[written++] = 'n';
break;
case 13:
destination[written++] = 'r';
break;
case 9:
destination[written++] = 't';
break;
case 92:
destination[written++] = '\\';
break;
case 8:
destination[written++] = 'b';
break;
case 12:
destination[written++] = 'f';
break;
default: {
destination[written++] = 'u';
int num = value;
num.TryFormat(destination.Slice(written), out int charsWritten, "X4".AsSpan(), null);
written += charsWritten;
break;
}
}
}
}
}