private static TValue? Read<TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo)
{
Debug.Assert(jsonTypeInfo.IsConfigured);
+
+ if (reader.CurrentState.Options.CommentHandling == JsonCommentHandling.Allow)
+ {
+ ThrowHelper.ThrowArgumentException_SerializerDoesNotSupportComments(nameof(reader));
+ }
+
ReadStack state = default;
state.Initialize(jsonTypeInfo);
+ Utf8JsonReader restore = reader;
- JsonReaderState readerState = reader.CurrentState;
- if (readerState.Options.CommentHandling == JsonCommentHandling.Allow)
+ try
{
- throw new ArgumentException(SR.JsonSerializerDoesNotSupportComments, nameof(reader));
+ Utf8JsonReader scopedReader = GetReaderScopedToNextValue(ref reader, ref state);
+ return ReadCore<TValue>(ref scopedReader, jsonTypeInfo, ref state);
+ }
+ catch (JsonException)
+ {
+ reader = restore;
+ throw;
}
+ }
- // Value copy to overwrite the ref on an exception and undo the destructive reads.
- Utf8JsonReader restore = reader;
+ private static Utf8JsonReader GetReaderScopedToNextValue(ref Utf8JsonReader reader, ref ReadStack state)
+ {
+ // Advances the provided reader, validating that it is pointing to a complete JSON value.
+ // If successful, returns a new Utf8JsonReader that is scoped to the next value, reusing existing buffers.
ReadOnlySpan<byte> valueSpan = default;
ReadOnlySequence<byte> valueSequence = default;
// Any of the "value start" states are acceptable.
case JsonTokenType.StartObject:
case JsonTokenType.StartArray:
- {
- long startingOffset = reader.TokenStartIndex;
-
- if (!reader.TrySkip())
- {
- ThrowHelper.ThrowJsonReaderException(ref reader, ExceptionResource.NotEnoughData);
- }
-
- long totalLength = reader.BytesConsumed - startingOffset;
- ReadOnlySequence<byte> sequence = reader.OriginalSequence;
+ long startingOffset = reader.TokenStartIndex;
- if (sequence.IsEmpty)
- {
- valueSpan = reader.OriginalSpan.Slice(
- checked((int)startingOffset),
- checked((int)totalLength));
- }
- else
- {
- valueSequence = sequence.Slice(startingOffset, totalLength);
- }
+ if (!reader.TrySkip())
+ {
+ ThrowHelper.ThrowJsonReaderException(ref reader, ExceptionResource.NotEnoughData);
+ }
- Debug.Assert(
- reader.TokenType == JsonTokenType.EndObject ||
- reader.TokenType == JsonTokenType.EndArray);
+ long totalLength = reader.BytesConsumed - startingOffset;
+ ReadOnlySequence<byte> sequence = reader.OriginalSequence;
- break;
+ if (sequence.IsEmpty)
+ {
+ valueSpan = reader.OriginalSpan.Slice(
+ checked((int)startingOffset),
+ checked((int)totalLength));
+ }
+ else
+ {
+ valueSequence = sequence.Slice(startingOffset, totalLength);
}
+ Debug.Assert(reader.TokenType is JsonTokenType.EndObject or JsonTokenType.EndArray);
+ break;
+
// Single-token values
case JsonTokenType.Number:
case JsonTokenType.True:
case JsonTokenType.False:
case JsonTokenType.Null:
+ if (reader.HasValueSequence)
{
- if (reader.HasValueSequence)
- {
- valueSequence = reader.ValueSequence;
- }
- else
- {
- valueSpan = reader.ValueSpan;
- }
-
- break;
+ valueSequence = reader.ValueSequence;
}
+ else
+ {
+ valueSpan = reader.ValueSpan;
+ }
+
+ break;
+
// String's ValueSequence/ValueSpan omits the quotes, we need them back.
case JsonTokenType.String:
+ ReadOnlySequence<byte> originalSequence = reader.OriginalSequence;
+
+ if (originalSequence.IsEmpty)
{
- ReadOnlySequence<byte> sequence = reader.OriginalSequence;
+ // Since the quoted string fit in a ReadOnlySpan originally
+ // the contents length plus the two quotes can't overflow.
+ int payloadLength = reader.ValueSpan.Length + 2;
+ Debug.Assert(payloadLength > 1);
- if (sequence.IsEmpty)
- {
- // Since the quoted string fit in a ReadOnlySpan originally
- // the contents length plus the two quotes can't overflow.
- int payloadLength = reader.ValueSpan.Length + 2;
- Debug.Assert(payloadLength > 1);
+ ReadOnlySpan<byte> readerSpan = reader.OriginalSpan;
- ReadOnlySpan<byte> readerSpan = reader.OriginalSpan;
+ Debug.Assert(
+ readerSpan[(int)reader.TokenStartIndex] == (byte)'"',
+ $"Calculated span starts with {readerSpan[(int)reader.TokenStartIndex]}");
- Debug.Assert(
- readerSpan[(int)reader.TokenStartIndex] == (byte)'"',
- $"Calculated span starts with {readerSpan[(int)reader.TokenStartIndex]}");
+ Debug.Assert(
+ readerSpan[(int)reader.TokenStartIndex + payloadLength - 1] == (byte)'"',
+ $"Calculated span ends with {readerSpan[(int)reader.TokenStartIndex + payloadLength - 1]}");
- Debug.Assert(
- readerSpan[(int)reader.TokenStartIndex + payloadLength - 1] == (byte)'"',
- $"Calculated span ends with {readerSpan[(int)reader.TokenStartIndex + payloadLength - 1]}");
+ valueSpan = readerSpan.Slice((int)reader.TokenStartIndex, payloadLength);
+ }
+ else
+ {
+ long payloadLength = reader.HasValueSequence
+ ? reader.ValueSequence.Length + 2
+ : reader.ValueSpan.Length + 2;
- valueSpan = readerSpan.Slice((int)reader.TokenStartIndex, payloadLength);
- }
- else
- {
- long payloadLength = 2;
-
- if (reader.HasValueSequence)
- {
- payloadLength += reader.ValueSequence.Length;
- }
- else
- {
- payloadLength += reader.ValueSpan.Length;
- }
-
- valueSequence = sequence.Slice(reader.TokenStartIndex, payloadLength);
- Debug.Assert(
- valueSequence.First.Span[0] == (byte)'"',
- $"Calculated sequence starts with {valueSequence.First.Span[0]}");
-
- Debug.Assert(
- valueSequence.ToArray()[payloadLength - 1] == (byte)'"',
- $"Calculated sequence ends with {valueSequence.ToArray()[payloadLength - 1]}");
- }
+ valueSequence = originalSequence.Slice(reader.TokenStartIndex, payloadLength);
+ Debug.Assert(
+ valueSequence.First.Span[0] == (byte)'"',
+ $"Calculated sequence starts with {valueSequence.First.Span[0]}");
- break;
+ Debug.Assert(
+ valueSequence.ToArray()[payloadLength - 1] == (byte)'"',
+ $"Calculated sequence ends with {valueSequence.ToArray()[payloadLength - 1]}");
}
- default:
- {
- byte displayByte;
- if (reader.HasValueSequence)
- {
- displayByte = reader.ValueSequence.First.Span[0];
- }
- else
- {
- displayByte = reader.ValueSpan[0];
- }
+ break;
- ThrowHelper.ThrowJsonReaderException(
- ref reader,
- ExceptionResource.ExpectedStartOfValueNotFound,
- displayByte);
+ default:
+ byte displayByte = reader.HasValueSequence
+ ? reader.ValueSequence.First.Span[0]
+ : reader.ValueSpan[0];
- break;
- }
+ ThrowHelper.ThrowJsonReaderException(
+ ref reader,
+ ExceptionResource.ExpectedStartOfValueNotFound,
+ displayByte);
+
+ break;
}
}
catch (JsonReaderException ex)
{
- reader = restore;
// Re-throw with Path information.
ThrowHelper.ReThrowWithPath(ref state, ex);
}
- int length = valueSpan.IsEmpty ? checked((int)valueSequence.Length) : valueSpan.Length;
- byte[] rented = ArrayPool<byte>.Shared.Rent(length);
- Span<byte> rentedSpan = rented.AsSpan(0, length);
-
- try
- {
- if (valueSpan.IsEmpty)
- {
- valueSequence.CopyTo(rentedSpan);
- }
- else
- {
- valueSpan.CopyTo(rentedSpan);
- }
-
- JsonReaderOptions originalReaderOptions = readerState.Options;
-
- var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions);
+ Debug.Assert(!valueSpan.IsEmpty ^ !valueSequence.IsEmpty);
- TValue? value = ReadCore<TValue>(ref newReader, jsonTypeInfo, ref state);
-
- // The reader should have thrown if we have remaining bytes.
- Debug.Assert(newReader.BytesConsumed == length);
-
- return value;
- }
- catch (JsonException)
- {
- reader = restore;
- throw;
- }
- finally
- {
- rentedSpan.Clear();
- ArrayPool<byte>.Shared.Return(rented);
- }
+ return valueSpan.IsEmpty
+ ? new Utf8JsonReader(valueSequence, reader.CurrentState.Options)
+ : new Utf8JsonReader(valueSpan, reader.CurrentState.Options);
}
}
}