JsonWriterHelper
using System.Buffers;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
namespace System.Text.Json
{
internal static class JsonWriterHelper
{
private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(false, true);
private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O', byte.MaxValue);
public const int LastAsciiCharacter = 127;
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 unsafe static bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
{
try {
if (!bytes.IsEmpty)
try {
fixed (byte* bytes2 = &bytes.GetPinnableReference()) {
s_utf8Encoding.GetCharCount(bytes2, bytes.Length);
}
} finally {
}
return true;
} catch (DecoderFallbackException) {
return false;
}
}
internal unsafe static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
{
written = 0;
try {
if (!source.IsEmpty)
try {
fixed (char* chars = &source.GetPinnableReference()) {
try {
fixed (byte* bytes = &destination.GetPinnableReference()) {
written = s_utf8Encoding.GetBytes(chars, source.Length, bytes, destination.Length);
}
} finally {
}
}
} finally {
}
return OperationStatus.Done;
} catch (EncoderFallbackException) {
return OperationStatus.InvalidData;
} catch (ArgumentException) {
return OperationStatus.DestinationTooSmall;
}
}
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 written)
{
if (encoder.EncodeUtf8(value, destination, out int _, out int bytesWritten, true) != 0)
ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(bytesWritten));
written += bytesWritten;
}
public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
{
value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
written = indexOfFirstByteToEscape;
if (encoder != null) {
destination = destination.Slice(indexOfFirstByteToEscape);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, encoder, ref written);
} 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++;
} else {
destination[written] = b;
written++;
indexOfFirstByteToEscape++;
}
}
destination = destination.Slice(written);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, JavaScriptEncoder.Default, ref written);
}
}
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 written)
{
if (encoder.Encode(value, destination, out int _, out int charsWritten, true) != 0)
ThrowHelper.ThrowArgumentException_InvalidUTF16(value[charsWritten]);
written += charsWritten;
}
public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
{
value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
written = indexOfFirstByteToEscape;
if (encoder != null) {
destination = destination.Slice(indexOfFirstByteToEscape);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, encoder, ref written);
} 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++;
} else {
destination[written] = c;
written++;
indexOfFirstByteToEscape++;
}
}
destination = destination.Slice(written);
value = value.Slice(indexOfFirstByteToEscape);
EscapeString(value, destination, JavaScriptEncoder.Default, ref written);
}
}
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';
written = WriteHex(value, destination, written);
break;
}
}
private static int WriteHex(int value, Span<char> destination, int written)
{
destination[written++] = System.HexConverter.ToCharUpper(value >> 12);
destination[written++] = System.HexConverter.ToCharUpper(value >> 8);
destination[written++] = System.HexConverter.ToCharUpper(value >> 4);
destination[written++] = System.HexConverter.ToCharUpper(value);
return written;
}
}
}