Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,39 @@ public static bool IsInRangeInclusive(JsonTokenType value, JsonTokenType lowerBo
/// </summary>
public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';

/// <summary>
/// Advances <paramref name="i"/> past any consecutive ASCII digit bytes ('0'..'9') in <paramref name="data"/>.
/// On return, <paramref name="nextByte"/> contains the first non-digit byte, or <see langword="default"/> if the
/// end of the span was reached.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SkipDigits(ReadOnlySpan<byte> data, ref int i, out byte nextByte)
{
#if NET
int nonDigitOffset = data.Slice(i).IndexOfAnyExceptInRange((byte)'0', (byte)'9');
if (nonDigitOffset < 0)
{
i = data.Length;
nextByte = default;
}
else
{
i += nonDigitOffset;
nextByte = data[i];
}
#else
nextByte = default;
for (; i < data.Length; i++)
{
nextByte = data[i];
if (!IsDigit(nextByte))
{
break;
}
}
#endif
}

/// <summary>
/// Perform a Read() with a Debug.Assert verifying the reader did not return false.
/// This should be called when the Read() return value is not used, such as non-Stream cases where there is only one buffer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1354,17 +1354,10 @@ private ConsumeNumberResult ConsumeZeroMultiSegment(ref ReadOnlySpan<byte> data,

private ConsumeNumberResult ConsumeIntegerDigitsMultiSegment(ref ReadOnlySpan<byte> data, scoped ref int i)
{
byte nextByte = default;
int counter = 0;
for (; i < data.Length; i++)
{
nextByte = data[i];
if (!JsonHelpers.IsDigit(nextByte))
{
break;
}
counter++;
}
byte nextByte;
int startIndex = i;
JsonHelpers.SkipDigits(data, ref i, out nextByte);
int counter = i - startIndex;
if (i >= data.Length)
{
if (IsLastSpan)
Expand Down Expand Up @@ -1394,14 +1387,7 @@ private ConsumeNumberResult ConsumeIntegerDigitsMultiSegment(ref ReadOnlySpan<by
HasValueSequence = true;
i = 0;
data = _buffer;
for (; i < data.Length; i++)
{
nextByte = data[i];
if (!JsonHelpers.IsDigit(nextByte))
{
break;
}
}
JsonHelpers.SkipDigits(data, ref i, out nextByte);
_bytePositionInLine += i;
if (i >= data.Length)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public ref partial struct Utf8JsonReader

private readonly bool IsLastSpan => _isFinalBlock && (!_isMultiSegment || _isLastSegment);

#if NET
private static readonly SearchValues<byte> s_whitespaceLookup = SearchValues.Create(" \t\n\r"u8);
#endif

internal readonly ReadOnlySequence<byte> OriginalSequence => _sequence;

internal readonly ReadOnlySpan<byte> OriginalSpan => _sequence.IsEmpty ? _buffer : default;
Expand Down Expand Up @@ -1008,6 +1012,36 @@ private void SkipWhiteSpace()
{
// Create local copy to avoid bounds checks.
ReadOnlySpan<byte> localBuffer = _buffer;

#if NET
// Use vectorized search to find the first non-whitespace byte.
// JSON RFC 8259 section 2 says only these 4 characters count as whitespace.
ReadOnlySpan<byte> remaining = localBuffer.Slice(_consumed);
int idx = remaining.IndexOfAnyExcept(s_whitespaceLookup);
if (idx < 0)
{
idx = remaining.Length;
}

if (idx > 0)
{
ReadOnlySpan<byte> whitespace = remaining.Slice(0, idx);
int newLineCount = whitespace.Count(JsonConstants.LineFeed);

if (newLineCount > 0)
{
_lineNumber += newLineCount;
int lastLF = whitespace.LastIndexOf(JsonConstants.LineFeed);
_bytePositionInLine = idx - lastLF - 1;
}
else
{
_bytePositionInLine += idx;
}

_consumed += idx;
}
#else
for (; _consumed < localBuffer.Length; _consumed++)
{
byte val = localBuffer[_consumed];
Expand All @@ -1031,6 +1065,7 @@ not JsonConstants.LineFeed and
_bytePositionInLine++;
}
}
#endif
}

/// <summary>
Expand Down Expand Up @@ -1603,15 +1638,7 @@ private ConsumeNumberResult ConsumeZero(ref ReadOnlySpan<byte> data, scoped ref

private ConsumeNumberResult ConsumeIntegerDigits(ref ReadOnlySpan<byte> data, scoped ref int i)
{
byte nextByte = default;
for (; i < data.Length; i++)
{
nextByte = data[i];
if (!JsonHelpers.IsDigit(nextByte))
{
break;
}
}
JsonHelpers.SkipDigits(data, ref i, out byte nextByte);
if (i >= data.Length)
{
if (IsLastSpan)
Expand Down
Loading