<PackageReference Include="System.Text.Json" Version="10.0.0-rc.1.25451.107" />

JsonWriterHelper

static class 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; } } } } }