< Summary

Line coverage
25%
Covered lines: 125
Uncovered lines: 364
Coverable lines: 489
Total lines: 868
Line coverage: 25.5%
Branch coverage
21%
Covered branches: 46
Total branches: 214
Branch coverage: 21.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: WriteIndentation(...)50%6647.36%
File 1: ValidateNewLine(...)66.66%6666.66%
File 1: ValidateIndentCharacter(...)0%440%
File 1: ValidateIndentSize(...)0%440%
File 1: ValidateProperty(...)0%220%
File 1: ValidateValue(...)50%2275%
File 1: ValidateDouble(...)0%220%
File 1: ValidateSingle(...)0%220%
File 1: ValidateProperty(...)0%220%
File 1: ValidateValue(...)50%2275%
File 1: ValidatePropertyAndValue(...)0%440%
File 1: ValidatePropertyAndValue(...)0%440%
File 1: ValidatePropertyAndValue(...)0%440%
File 1: ValidatePropertyAndValue(...)0%440%
File 1: ValidatePropertyNameLength(...)0%220%
File 1: ValidatePropertyNameLength(...)0%220%
File 1: ValidateNumber(...)0%40400%
File 1: IsValidUtf8String(...)100%110%
File 1: ToUtf8(...)50%66100%
File 1: WriteString(...)0%10100%
File 2: .cctor()100%11100%
File 2: WriteDateTimeTrimmed(...)100%110%
File 2: WriteDateTimeOffsetTrimmed(...)100%110%
File 2: TrimDateTimeOffset(...)0%22220%
File 3: .cctor()100%22100%
File 3: NeedsEscaping(...)100%110%
File 3: NeedsEscapingNoBoundsCheck(...)100%110%
File 3: NeedsEscaping(...)66.66%66100%
File 3: NeedsEscaping(...)62.5%8883.33%
File 3: GetMaxEscapedLength(...)50%22100%
File 3: EscapeString(...)75%121283.33%
File 3: EscapeString(...)100%110%
File 3: EscapeString(...)20%101031.57%
File 3: EscapeNextBytes(...)0%11110%
File 3: IsAsciiValue(...)100%110%
File 3: IsAsciiValue(...)100%110%
File 3: EscapeString(...)75%121283.33%
File 3: EscapeString(...)100%110%
File 3: EscapeString(...)20%101031.57%
File 3: EscapeNextChars(...)0%11110%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7using System.Text.Unicode;
 8
 9namespace System.Text.Json
 10{
 11    internal static partial class JsonWriterHelper
 12    {
 13        public static void WriteIndentation(Span<byte> buffer, int indent, byte indentByte)
 140714        {
 140715            Debug.Assert(buffer.Length >= indent);
 16
 17            // Based on perf tests, the break-even point where vectorized Fill is faster
 18            // than explicitly writing the space in a loop is 8.
 140719            if (indent < 8)
 140720            {
 140721                int i = 0;
 140722                while (i + 1 < indent)
 023                {
 024                    buffer[i++] = indentByte;
 025                    buffer[i++] = indentByte;
 026                }
 27
 140728                if (i < indent)
 029                {
 030                    buffer[i] = indentByte;
 031                }
 140732            }
 33            else
 034            {
 035                buffer.Slice(0, indent).Fill(indentByte);
 036            }
 140737        }
 38
 39        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 40        public static void ValidateNewLine(string value)
 50141        {
 50142            if (value is null)
 043                ThrowHelper.ThrowArgumentNullException(nameof(value));
 44
 50145            if (value is not JsonConstants.NewLineLineFeed and not JsonConstants.NewLineCarriageReturnLineFeed)
 046                ThrowHelper.ThrowArgumentOutOfRangeException_NewLine(nameof(value));
 50147        }
 48
 49        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 50        public static void ValidateIndentCharacter(char value)
 051        {
 052            if (value is not JsonConstants.DefaultIndentCharacter and not JsonConstants.TabIndentCharacter)
 053                ThrowHelper.ThrowArgumentOutOfRangeException_IndentCharacter(nameof(value));
 054        }
 55
 56        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 57        public static void ValidateIndentSize(int value)
 058        {
 059            if (value is < JsonConstants.MinimumIndentSize or > JsonConstants.MaximumIndentSize)
 060                ThrowHelper.ThrowArgumentOutOfRangeException_IndentSize(nameof(value), JsonConstants.MinimumIndentSize, 
 061        }
 62
 63        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 64        public static void ValidateProperty(ReadOnlySpan<byte> propertyName)
 065        {
 066            if (propertyName.Length > JsonConstants.MaxUnescapedTokenSize)
 067                ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
 068        }
 69
 70        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 71        public static void ValidateValue(ReadOnlySpan<byte> value)
 157072        {
 157073            if (value.Length > JsonConstants.MaxUnescapedTokenSize)
 074                ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
 157075        }
 76
 77        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 78        public static void ValidateDouble(double value)
 079        {
 080            if (!double.IsFinite(value))
 081            {
 082                ThrowHelper.ThrowArgumentException_ValueNotSupported();
 83            }
 084        }
 85
 86        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 87        public static void ValidateSingle(float value)
 088        {
 089            if (!float.IsFinite(value))
 090            {
 091                ThrowHelper.ThrowArgumentException_ValueNotSupported();
 92            }
 093        }
 94
 95        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 96        public static void ValidateProperty(ReadOnlySpan<char> propertyName)
 097        {
 098            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize)
 099                ThrowHelper.ThrowArgumentException_PropertyNameTooLarge(propertyName.Length);
 0100        }
 101
 102        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 103        public static void ValidateValue(ReadOnlySpan<char> value)
 2560104        {
 2560105            if (value.Length > JsonConstants.MaxCharacterTokenSize)
 0106                ThrowHelper.ThrowArgumentException_ValueTooLarge(value.Length);
 2560107        }
 108
 109        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 110        public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
 0111        {
 0112            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || value.Length > JsonConstants.MaxUnescapedTo
 0113                ThrowHelper.ThrowArgumentException(propertyName, value);
 0114        }
 115
 116        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 117        public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<char> value)
 0118        {
 0119            if (propertyName.Length > JsonConstants.MaxUnescapedTokenSize || value.Length > JsonConstants.MaxCharacterTo
 0120                ThrowHelper.ThrowArgumentException(propertyName, value);
 0121        }
 122
 123        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 124        public static void ValidatePropertyAndValue(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> value)
 0125        {
 0126            if (propertyName.Length > JsonConstants.MaxUnescapedTokenSize || value.Length > JsonConstants.MaxUnescapedTo
 0127                ThrowHelper.ThrowArgumentException(propertyName, value);
 0128        }
 129
 130        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 131        public static void ValidatePropertyAndValue(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
 0132        {
 0133            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || value.Length > JsonConstants.MaxCharacterTo
 0134                ThrowHelper.ThrowArgumentException(propertyName, value);
 0135        }
 136
 137        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 138        public static void ValidatePropertyNameLength(ReadOnlySpan<char> propertyName)
 0139        {
 0140            if (propertyName.Length > JsonConstants.MaxCharacterTokenSize)
 0141                ThrowHelper.ThrowPropertyNameTooLargeArgumentException(propertyName.Length);
 0142        }
 143
 144        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 145        public static void ValidatePropertyNameLength(ReadOnlySpan<byte> propertyName)
 0146        {
 0147            if (propertyName.Length > JsonConstants.MaxUnescapedTokenSize)
 0148                ThrowHelper.ThrowPropertyNameTooLargeArgumentException(propertyName.Length);
 0149        }
 150
 151        internal static void ValidateNumber(ReadOnlySpan<byte> utf8FormattedNumber)
 0152        {
 153            // This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber,
 154            // because it doesn't need to deal with "NeedsMoreData", or remembering the format.
 155            //
 156            // The Debug.Asserts in this method should change to validated ArgumentExceptions if/when
 157            // writing a formatted number becomes public API.
 0158            Debug.Assert(!utf8FormattedNumber.IsEmpty);
 159
 0160            int i = 0;
 161
 0162            if (utf8FormattedNumber[i] == '-')
 0163            {
 0164                i++;
 165
 0166                if (utf8FormattedNumber.Length <= i)
 0167                {
 0168                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
 169                }
 0170            }
 171
 0172            if (utf8FormattedNumber[i] == '0')
 0173            {
 0174                i++;
 0175            }
 176            else
 0177            {
 0178                while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
 0179                {
 0180                    i++;
 0181                }
 0182            }
 183
 0184            if (i == utf8FormattedNumber.Length)
 0185            {
 0186                return;
 187            }
 188
 189            // The non digit character inside the number
 0190            byte val = utf8FormattedNumber[i];
 191
 0192            if (val == '.')
 0193            {
 0194                i++;
 195
 0196                if (utf8FormattedNumber.Length <= i)
 0197                {
 0198                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
 199                }
 200
 0201                while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
 0202                {
 0203                    i++;
 0204                }
 205
 0206                if (i == utf8FormattedNumber.Length)
 0207                {
 0208                    return;
 209                }
 210
 0211                Debug.Assert(i < utf8FormattedNumber.Length);
 0212                val = utf8FormattedNumber[i];
 0213            }
 214
 0215            if (val is (byte)'e' or (byte)'E')
 0216            {
 0217                i++;
 218
 0219                if (utf8FormattedNumber.Length <= i)
 0220                {
 0221                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
 222                }
 223
 0224                val = utf8FormattedNumber[i];
 225
 0226                if (val is (byte)'+' or (byte)'-')
 0227                {
 0228                    i++;
 0229                }
 0230            }
 231            else
 0232            {
 0233                throw new ArgumentException(
 0234                    SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(val)),
 0235                    nameof(utf8FormattedNumber));
 236            }
 237
 0238            if (utf8FormattedNumber.Length <= i)
 0239            {
 0240                throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
 241            }
 242
 0243            while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
 0244            {
 0245                i++;
 0246            }
 247
 0248            if (i != utf8FormattedNumber.Length)
 0249            {
 0250                throw new ArgumentException(
 0251                    SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])),
 0252                    nameof(utf8FormattedNumber));
 253            }
 0254        }
 255
 256#if !NET
 257        private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, t
 258#endif
 259
 260        public static bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
 0261        {
 262#if NET
 0263            return Utf8.IsValid(bytes);
 264#else
 265            try
 266            {
 267                _ = s_utf8Encoding.GetCharCount(bytes);
 268                return true;
 269            }
 270            catch (DecoderFallbackException)
 271            {
 272                return false;
 273            }
 274#endif
 0275        }
 276
 277        internal static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
 2013278        {
 279#if NET
 2013280            OperationStatus status = Utf8.FromUtf16(source, destination, out int charsRead, out written, replaceInvalidS
 2013281            Debug.Assert(status is OperationStatus.Done or OperationStatus.DestinationTooSmall or OperationStatus.Invali
 2013282            Debug.Assert(charsRead == source.Length || status is not OperationStatus.Done);
 2013283            return status;
 284#else
 285            written = 0;
 286            try
 287            {
 288                written = s_utf8Encoding.GetBytes(source, destination);
 289                return OperationStatus.Done;
 290            }
 291            catch (EncoderFallbackException)
 292            {
 293                return OperationStatus.InvalidData;
 294            }
 295            catch (ArgumentException)
 296            {
 297                return OperationStatus.DestinationTooSmall;
 298            }
 299#endif
 2013300        }
 301
 302        internal delegate T WriteCallback<T>(ReadOnlySpan<byte> serializedValue);
 303
 304        internal static unsafe T WriteString<T>(ReadOnlySpan<byte> utf8Value, WriteCallback<T> writeCallback)
 0305        {
 0306            int firstByteToEscape = JsonWriterHelper.NeedsEscaping(utf8Value, encoder: null);
 307
 0308            if (firstByteToEscape == -1)
 0309            {
 0310                int quotedLength = utf8Value.Length + 2;
 0311                byte[]? rented = null;
 312
 313                try
 0314                {
 0315                    Span<byte> quotedValue = quotedLength > JsonConstants.StackallocByteThreshold
 0316                        ? (rented = ArrayPool<byte>.Shared.Rent(quotedLength)).AsSpan(0, quotedLength)
 0317                        : stackalloc byte[JsonConstants.StackallocByteThreshold].Slice(0, quotedLength);
 318
 0319                    quotedValue[0] = JsonConstants.Quote;
 0320                    utf8Value.CopyTo(quotedValue.Slice(1));
 0321                    quotedValue[quotedValue.Length - 1] = JsonConstants.Quote;
 322
 0323                    return writeCallback(quotedValue);
 324                }
 325                finally
 0326                {
 0327                    if (rented is not null)
 0328                    {
 0329                        ArrayPool<byte>.Shared.Return(rented);
 0330                    }
 0331                }
 332            }
 333            else
 0334            {
 0335                Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
 336
 0337                int length = checked(2 + JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstByteToEscape));
 0338                byte[]? rented = null;
 339
 340                try
 0341                {
 342                    scoped Span<byte> escapedValue;
 343
 0344                    if (length > JsonConstants.StackallocByteThreshold)
 0345                    {
 0346                        rented = ArrayPool<byte>.Shared.Rent(length);
 0347                        escapedValue = rented;
 0348                    }
 349                    else
 0350                    {
 0351                        escapedValue = stackalloc byte[JsonConstants.StackallocByteThreshold];
 0352                    }
 353
 0354                    escapedValue[0] = JsonConstants.Quote;
 0355                    JsonWriterHelper.EscapeString(utf8Value, escapedValue.Slice(1), firstByteToEscape, encoder: null, ou
 0356                    escapedValue[1 + written] = JsonConstants.Quote;
 357
 0358                    return writeCallback(escapedValue.Slice(0, written + 2));
 359                }
 360                finally
 0361                {
 0362                    if (rented is not null)
 0363                    {
 0364                        ArrayPool<byte>.Shared.Return(rented);
 0365                    }
 0366                }
 367            }
 0368        }
 369    }
 370}
 371

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Text;
 6using System.Diagnostics;
 7using System.Runtime.CompilerServices;
 8
 9namespace System.Text.Json
 10{
 11    internal static partial class JsonWriterHelper
 12    {
 113        private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O');
 14
 15        public static unsafe void WriteDateTimeTrimmed(Span<byte> buffer, DateTime value, out int bytesWritten)
 016        {
 017            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
 018            bool result = Utf8Formatter.TryFormat(value, tempSpan, out bytesWritten, s_dateTimeStandardFormat);
 019            Debug.Assert(result);
 020            TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
 021            tempSpan.Slice(0, bytesWritten).CopyTo(buffer);
 022        }
 23
 24        public static unsafe void WriteDateTimeOffsetTrimmed(Span<byte> buffer, DateTimeOffset value, out int bytesWritt
 025        {
 026            Span<byte> tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength];
 027            bool result = Utf8Formatter.TryFormat(value, tempSpan, out bytesWritten, s_dateTimeStandardFormat);
 028            Debug.Assert(result);
 029            TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten);
 030            tempSpan.Slice(0, bytesWritten).CopyTo(buffer);
 031        }
 32
 33        //
 34        // Trims roundtrippable DateTime(Offset) input.
 35        // If the milliseconds part of the date is zero, we omit the fraction part of the date,
 36        // else we write the fraction up to 7 decimal places with no trailing zeros. i.e. the output format is
 37        // YYYY-MM-DDThh:mm:ss[.s]TZD where TZD = Z or +-hh:mm.
 38        // e.g.
 39        //   ---------------------------------
 40        //   2017-06-12T05:30:45.768-07:00
 41        //   2017-06-12T05:30:45.00768Z           (Z is short for "+00:00" but also distinguishes DateTimeKind.Utc from 
 42        //   2017-06-12T05:30:45                  (interpreted as local time wrt to current time zone)
 43        public static void TrimDateTimeOffset(Span<byte> buffer, out int bytesWritten)
 044        {
 45            const int maxDateTimeLength = JsonConstants.MaximumFormatDateTimeLength;
 46
 47            // Assert buffer is the right length for:
 48            // YYYY-MM-DDThh:mm:ss.fffffff (JsonConstants.MaximumFormatDateTimeLength)
 49            // YYYY-MM-DDThh:mm:ss.fffffffZ (JsonConstants.MaximumFormatDateTimeLength + 1)
 50            // YYYY-MM-DDThh:mm:ss.fffffff(+|-)hh:mm (JsonConstants.MaximumFormatDateTimeOffsetLength)
 051            Debug.Assert(buffer.Length == maxDateTimeLength ||
 052                buffer.Length == maxDateTimeLength + 1 ||
 053                buffer.Length == JsonConstants.MaximumFormatDateTimeOffsetLength);
 54
 55            // Find the last significant digit.
 56            int curIndex;
 057            if (buffer[maxDateTimeLength - 1] == '0')
 058                if (buffer[maxDateTimeLength - 2] == '0')
 059                    if (buffer[maxDateTimeLength - 3] == '0')
 060                        if (buffer[maxDateTimeLength - 4] == '0')
 061                            if (buffer[maxDateTimeLength - 5] == '0')
 062                                if (buffer[maxDateTimeLength - 6] == '0')
 063                                    if (buffer[maxDateTimeLength - 7] == '0')
 064                                    {
 65                                        // All decimal places are 0 so we can delete the decimal point too.
 066                                        curIndex = maxDateTimeLength - 7 - 1;
 067                                    }
 068                                    else { curIndex = maxDateTimeLength - 6; }
 069                                else { curIndex = maxDateTimeLength - 5; }
 070                            else { curIndex = maxDateTimeLength - 4; }
 071                        else { curIndex = maxDateTimeLength - 3; }
 072                    else { curIndex = maxDateTimeLength - 2; }
 073                else { curIndex = maxDateTimeLength - 1; }
 74            else
 075            {
 76                // There is nothing to trim.
 077                bytesWritten = buffer.Length;
 078                return;
 79            }
 80
 81            // We are either trimming a DateTimeOffset, or a DateTime with
 82            // DateTimeKind.Local or DateTimeKind.Utc
 083            if (buffer.Length == maxDateTimeLength)
 084            {
 85                // There is no offset to copy.
 086                bytesWritten = curIndex;
 087            }
 088            else if (buffer.Length == JsonConstants.MaximumFormatDateTimeOffsetLength)
 089            {
 90                // We have a non-UTC offset (+|-)hh:mm that are 6 characters to copy.
 091                buffer[curIndex] = buffer[maxDateTimeLength];
 092                buffer[curIndex + 1] = buffer[maxDateTimeLength + 1];
 093                buffer[curIndex + 2] = buffer[maxDateTimeLength + 2];
 094                buffer[curIndex + 3] = buffer[maxDateTimeLength + 3];
 095                buffer[curIndex + 4] = buffer[maxDateTimeLength + 4];
 096                buffer[curIndex + 5] = buffer[maxDateTimeLength + 5];
 097                bytesWritten = curIndex + 6;
 098            }
 99            else
 0100            {
 101                // There is a single 'Z'. Just write it at the current index.
 0102                Debug.Assert(buffer[maxDateTimeLength] == 'Z');
 103
 0104                buffer[curIndex] = (byte)'Z';
 0105                bytesWritten = curIndex + 1;
 0106            }
 0107        }
 108    }
 109}
 110

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Text;
 6using System.Diagnostics;
 7using System.Text.Encodings.Web;
 8
 9#if !NET
 10using System.Runtime.CompilerServices;
 11#endif
 12
 13namespace System.Text.Json
 14{
 15    internal static partial class JsonWriterHelper
 16    {
 17        // Only allow ASCII characters between ' ' (0x20) and '~' (0x7E), inclusively,
 18        // but exclude characters that need to be escaped as hex: '"', '\'', '&', '+', '<', '>', '`'
 19        // and exclude characters that need to be escaped by adding a backslash: '\n', '\r', '\t', '\\', '\b', '\f'
 20        //
 21        // non-zero = allowed, 0 = disallowed
 22        // This exactly matches the set used by JavaScriptEncoder.Default.
 23        // SearchValues instances below also represent the same ASCII set, validated to match in the debug static ctor b
 24        public const int LastAsciiCharacter = 0x7F;
 25        private static ReadOnlySpan<byte> AllowList => // byte.MaxValue + 1
 12826        [
 12827            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // U+0000..U+000F
 12828            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // U+0010..U+001F
 12829            1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, // U+0020..U+002F
 12830            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // U+0030..U+003F
 12831            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // U+0040..U+004F
 12832            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // U+0050..U+005F
 12833            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // U+0060..U+006F
 12834            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // U+0070..U+007F
 12835
 12836            // Also include the ranges from U+0080 to U+00FF for performance to avoid UTF8 code from checking boundary.
 12837            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12838            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12839            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12840            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12841            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12842            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12843            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 12844            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // U+00F0..U+00FF
 12845        ];
 46
 47#if DEBUG && NET
 48        static JsonWriterHelper()
 149        {
 25850            for (int i = 0; i < 128; i++)
 12851            {
 12852                bool needsEscaping = AllowList[i] == 0;
 12853                Debug.Assert(needsEscaping == JavaScriptEncoder.Default.WillEncode(i));
 12854                Debug.Assert(needsEscaping == !s_allowedBytes.Contains((byte)i));
 12855                Debug.Assert(needsEscaping == !s_allowedChars.Contains((char)i));
 12856            }
 157        }
 58#endif
 59
 60#if NET
 61        private const string HexFormatString = "X4";
 62#endif
 63
 164        private static readonly StandardFormat s_hexStandardFormat = new StandardFormat('X', 4);
 65
 066        private static bool NeedsEscaping(byte value) => AllowList[value] == 0;
 67
 068        private static bool NeedsEscapingNoBoundsCheck(char value) => AllowList[value] == 0;
 69
 70#if NET
 71        // The characters allowed by AllowList, used to vectorize the default (no custom encoder) escaping
 72        // scan directly instead of routing through System.Text.Encodings.Web.
 73        // Validated to match the AllowList and JavaScriptEncoder.Default in the static ctor above.
 174        private static readonly SearchValues<byte> s_allowedBytes = SearchValues.Create(
 175            " !#$%()*,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"u8);
 176        private static readonly SearchValues<char> s_allowedChars = SearchValues.Create(
 177            " !#$%()*,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~");
 78#endif
 79
 80        public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder? encoder)
 96181        {
 82#if NET
 96183            if (encoder is null || ReferenceEquals(encoder, JavaScriptEncoder.Default))
 67584            {
 67585                return value.IndexOfAnyExcept(s_allowedBytes);
 86            }
 87#endif
 88
 28689            return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value);
 96190        }
 91
 92        public static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder? encoder)
 201393        {
 94#if NET
 201395            if (encoder is null || ReferenceEquals(encoder, JavaScriptEncoder.Default))
 124696            {
 124697                return value.IndexOfAnyExcept(s_allowedChars);
 98            }
 99#endif
 100
 101            // Some implementations of JavaScriptEncoder.FindFirstCharacterToEncode may not accept
 102            // null pointers and guard against that. Hence, check up-front to return -1.
 767103            if (value.IsEmpty)
 0104            {
 0105                return -1;
 106            }
 107
 108            // Unfortunately, there is no public API for FindFirstCharacterToEncode(Span<char>) yet,
 109            // so we have to use the unsafe FindFirstCharacterToEncode(char*, int) instead.
 110            unsafe
 767111            {
 767112                fixed (char* ptr = value)
 767113                {
 767114                    return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(ptr, value.Length);
 115                }
 116            }
 2013117        }
 118
 119        public static int GetMaxEscapedLength(int textLength, int firstIndexToEscape)
 2716120        {
 2716121            Debug.Assert(textLength > 0);
 2716122            Debug.Assert(firstIndexToEscape >= 0 && firstIndexToEscape < textLength);
 2716123            return firstIndexToEscape + JsonConstants.MaxExpansionFactorWhileEscaping * (textLength - firstIndexToEscape
 2716124        }
 125
 126        private static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, JavaScriptEncoder encoder, re
 896127        {
 896128            Debug.Assert(encoder is not null);
 129
 896130            OperationStatus result = encoder.EncodeUtf8(value, destination, out int encoderBytesConsumed, out int encode
 131
 896132            Debug.Assert(result != OperationStatus.DestinationTooSmall);
 896133            Debug.Assert(result != OperationStatus.NeedMoreData || !isFinalBlock);
 134
 896135            if (!(result == OperationStatus.Done || (result == OperationStatus.NeedMoreData && !isFinalBlock)))
 0136            {
 0137                ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesWritten));
 138            }
 139
 896140            Debug.Assert(encoderBytesConsumed == value.Length || (result == OperationStatus.NeedMoreData && !isFinalBloc
 141
 896142            written += encoderBytesWritten;
 896143            consumed += encoderBytesConsumed;
 896144        }
 145
 146        public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, 
 0147            => EscapeString(value, destination, indexOfFirstByteToEscape, encoder, out _, out written, isFinalBlock: tru
 148
 149        public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, 
 896150        {
 896151            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
 152
 896153            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
 896154            written = indexOfFirstByteToEscape;
 896155            consumed = indexOfFirstByteToEscape;
 156
 896157            if (encoder is not null)
 896158            {
 896159                destination = destination.Slice(indexOfFirstByteToEscape);
 896160                value = value.Slice(indexOfFirstByteToEscape);
 896161                EscapeString(value, destination, encoder, ref consumed, ref written, isFinalBlock);
 896162            }
 163            else
 0164            {
 165                // For performance when no encoder is specified, perform escaping here for Ascii and on the
 166                // first occurrence of a non-Ascii character, then call into the default encoder.
 0167                while (indexOfFirstByteToEscape < value.Length)
 0168                {
 0169                    byte val = value[indexOfFirstByteToEscape];
 0170                    if (IsAsciiValue(val))
 0171                    {
 0172                        if (NeedsEscaping(val))
 0173                        {
 0174                            EscapeNextBytes(val, destination, ref written);
 0175                            indexOfFirstByteToEscape++;
 0176                            consumed++;
 0177                        }
 178                        else
 0179                        {
 0180                            destination[written] = val;
 0181                            written++;
 0182                            indexOfFirstByteToEscape++;
 0183                            consumed++;
 0184                        }
 0185                    }
 186                    else
 0187                    {
 188                        // Fall back to default encoder.
 0189                        destination = destination.Slice(written);
 0190                        value = value.Slice(indexOfFirstByteToEscape);
 0191                        EscapeString(value, destination, JavaScriptEncoder.Default, ref consumed, ref written, isFinalBl
 0192                        break;
 193                    }
 0194                }
 0195            }
 896196        }
 197
 198        private static void EscapeNextBytes(byte value, Span<byte> destination, ref int totalWritten)
 0199        {
 200            // Slice the span so the JIT can see that no bounds checks are needed below
 0201            destination = destination.Slice(totalWritten, JsonConstants.MaxExpansionFactorWhileEscaping);
 0202            int written = 0;
 203
 0204            destination[written++] = (byte)'\\';
 0205            switch (value)
 206            {
 207                case JsonConstants.Quote:
 208                    // Optimize for the common quote case.
 0209                    destination[written++] = (byte)'u';
 0210                    destination[written++] = (byte)'0';
 0211                    destination[written++] = (byte)'0';
 0212                    destination[written++] = (byte)'2';
 0213                    destination[written++] = (byte)'2';
 0214                    break;
 215                case JsonConstants.LineFeed:
 0216                    destination[written++] = (byte)'n';
 0217                    break;
 218                case JsonConstants.CarriageReturn:
 0219                    destination[written++] = (byte)'r';
 0220                    break;
 221                case JsonConstants.Tab:
 0222                    destination[written++] = (byte)'t';
 0223                    break;
 224                case JsonConstants.BackSlash:
 0225                    destination[written++] = (byte)'\\';
 0226                    break;
 227                case JsonConstants.BackSpace:
 0228                    destination[written++] = (byte)'b';
 0229                    break;
 230                case JsonConstants.FormFeed:
 0231                    destination[written++] = (byte)'f';
 0232                    break;
 233                default:
 0234                    destination[written++] = (byte)'u';
 235
 0236                    bool result = Utf8Formatter.TryFormat(value, destination.Slice(written), out int bytesWritten, forma
 0237                    Debug.Assert(result);
 0238                    Debug.Assert(bytesWritten == 4);
 0239                    written += bytesWritten;
 0240                    break;
 241            }
 242
 0243            totalWritten += written;
 0244        }
 245
 0246        private static bool IsAsciiValue(byte value) => value <= LastAsciiCharacter;
 247
 0248        private static bool IsAsciiValue(char value) => value <= LastAsciiCharacter;
 249
 250        private static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, JavaScriptEncoder encoder, re
 1820251        {
 1820252            Debug.Assert(encoder is not null);
 253
 1820254            OperationStatus result = encoder.Encode(value, destination, out int encoderBytesConsumed, out int encoderCha
 255
 1820256            Debug.Assert(result != OperationStatus.DestinationTooSmall);
 1820257            Debug.Assert(result != OperationStatus.NeedMoreData || !isFinalBlock);
 258
 1820259            if (!(result == OperationStatus.Done || (result == OperationStatus.NeedMoreData && !isFinalBlock)))
 0260            {
 0261                ThrowHelper.ThrowArgumentException_InvalidUTF16(value[encoderCharsWritten]);
 262            }
 263
 1820264            Debug.Assert(encoderBytesConsumed == value.Length || (result == OperationStatus.NeedMoreData && !isFinalBloc
 265
 1820266            written += encoderCharsWritten;
 1820267            consumed += encoderBytesConsumed;
 1820268        }
 269
 270        public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, 
 0271            => EscapeString(value, destination, indexOfFirstByteToEscape, encoder, out _, out written, isFinalBlock: tru
 272
 273        public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, 
 1820274        {
 1820275            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
 276
 1820277            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
 1820278            written = indexOfFirstByteToEscape;
 1820279            consumed = indexOfFirstByteToEscape;
 280
 1820281            if (encoder is not null)
 1820282            {
 1820283                destination = destination.Slice(indexOfFirstByteToEscape);
 1820284                value = value.Slice(indexOfFirstByteToEscape);
 1820285                EscapeString(value, destination, encoder, ref consumed, ref written, isFinalBlock);
 1820286            }
 287            else
 0288            {
 289                // For performance when no encoder is specified, perform escaping here for Ascii and on the
 290                // first occurrence of a non-Ascii character, then call into the default encoder.
 0291                while (indexOfFirstByteToEscape < value.Length)
 0292                {
 0293                    char val = value[indexOfFirstByteToEscape];
 0294                    if (IsAsciiValue(val))
 0295                    {
 0296                        if (NeedsEscapingNoBoundsCheck(val))
 0297                        {
 0298                            EscapeNextChars(val, destination, ref written);
 0299                            indexOfFirstByteToEscape++;
 0300                            consumed++;
 0301                        }
 302                        else
 0303                        {
 0304                            destination[written] = val;
 0305                            written++;
 0306                            indexOfFirstByteToEscape++;
 0307                            consumed++;
 0308                        }
 0309                    }
 310                    else
 0311                    {
 312                        // Fall back to default encoder.
 0313                        destination = destination.Slice(written);
 0314                        value = value.Slice(indexOfFirstByteToEscape);
 0315                        EscapeString(value, destination, JavaScriptEncoder.Default, ref consumed, ref written, isFinalBl
 0316                        break;
 317                    }
 0318                }
 0319            }
 1820320        }
 321
 322        private static void EscapeNextChars(char value, Span<char> destination, ref int totalWritten)
 0323        {
 0324            Debug.Assert(IsAsciiValue(value));
 325
 326            // Slice the span so the JIT can see that no bounds checks are needed below
 0327            destination = destination.Slice(totalWritten, JsonConstants.MaxExpansionFactorWhileEscaping);
 0328            int written = 0;
 329
 0330            destination[written++] = '\\';
 0331            switch ((byte)value)
 332            {
 333                case JsonConstants.Quote:
 334                    // Optimize for the common quote case.
 0335                    destination[written++] = 'u';
 0336                    destination[written++] = '0';
 0337                    destination[written++] = '0';
 0338                    destination[written++] = '2';
 0339                    destination[written++] = '2';
 0340                    break;
 341                case JsonConstants.LineFeed:
 0342                    destination[written++] = 'n';
 0343                    break;
 344                case JsonConstants.CarriageReturn:
 0345                    destination[written++] = 'r';
 0346                    break;
 347                case JsonConstants.Tab:
 0348                    destination[written++] = 't';
 0349                    break;
 350                case JsonConstants.BackSlash:
 0351                    destination[written++] = '\\';
 0352                    break;
 353                case JsonConstants.BackSpace:
 0354                    destination[written++] = 'b';
 0355                    break;
 356                case JsonConstants.FormFeed:
 0357                    destination[written++] = 'f';
 0358                    break;
 359                default:
 0360                    destination[written++] = 'u';
 361#if NET
 0362                    int intChar = value;
 0363                    intChar.TryFormat(destination.Slice(written), out int charsWritten, HexFormatString);
 0364                    Debug.Assert(charsWritten == 4);
 0365                    written += charsWritten;
 366#else
 367                    written = WriteHex(value, destination, written);
 368#endif
 0369                    break;
 370            }
 371
 0372            totalWritten += written;
 0373        }
 374
 375#if !NET
 376        private static int WriteHex(int value, Span<char> destination, int written)
 377        {
 378            destination[written++] = HexConverter.ToCharUpper(value >> 12);
 379            destination[written++] = HexConverter.ToCharUpper(value >> 8);
 380            destination[written++] = HexConverter.ToCharUpper(value >> 4);
 381            destination[written++] = HexConverter.ToCharUpper(value);
 382            return written;
 383        }
 384#endif
 385    }
 386}
 387

Methods/Properties

WriteIndentation(System.Span`1<System.Byte>,System.Int32,System.Byte)
ValidateNewLine(System.String)
ValidateIndentCharacter(System.Char)
ValidateIndentSize(System.Int32)
ValidateProperty(System.ReadOnlySpan`1<System.Byte>)
ValidateValue(System.ReadOnlySpan`1<System.Byte>)
ValidateDouble(System.Double)
ValidateSingle(System.Single)
ValidateProperty(System.ReadOnlySpan`1<System.Char>)
ValidateValue(System.ReadOnlySpan`1<System.Char>)
ValidatePropertyAndValue(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Byte>)
ValidatePropertyAndValue(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Char>)
ValidatePropertyAndValue(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
ValidatePropertyAndValue(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
ValidatePropertyNameLength(System.ReadOnlySpan`1<System.Char>)
ValidatePropertyNameLength(System.ReadOnlySpan`1<System.Byte>)
ValidateNumber(System.ReadOnlySpan`1<System.Byte>)
IsValidUtf8String(System.ReadOnlySpan`1<System.Byte>)
ToUtf8(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Int32&)
WriteString(System.ReadOnlySpan`1<System.Byte>,System.Text.Json.JsonWriterHelper/WriteCallback`1<T>)
.cctor()
WriteDateTimeTrimmed(System.Span`1<System.Byte>,System.DateTime,System.Int32&)
WriteDateTimeOffsetTrimmed(System.Span`1<System.Byte>,System.DateTimeOffset,System.Int32&)
TrimDateTimeOffset(System.Span`1<System.Byte>,System.Int32&)
AllowList()
.cctor()
NeedsEscaping(System.Byte)
NeedsEscapingNoBoundsCheck(System.Char)
NeedsEscaping(System.ReadOnlySpan`1<System.Byte>,System.Text.Encodings.Web.JavaScriptEncoder)
NeedsEscaping(System.ReadOnlySpan`1<System.Char>,System.Text.Encodings.Web.JavaScriptEncoder)
GetMaxEscapedLength(System.Int32,System.Int32)
EscapeString(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&,System.Int32&,System.Boolean)
EscapeString(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&)
EscapeString(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&,System.Int32&,System.Boolean)
EscapeNextBytes(System.Byte,System.Span`1<System.Byte>,System.Int32&)
IsAsciiValue(System.Byte)
IsAsciiValue(System.Char)
EscapeString(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&,System.Int32&,System.Boolean)
EscapeString(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&)
EscapeString(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder,System.Int32&,System.Int32&,System.Boolean)
EscapeNextChars(System.Char,System.Span`1<System.Char>,System.Int32&)