private static async Task <JsonDocument> ParseAsyncCore( Stream utf8Json, JsonReaderOptions readerOptions = default, CancellationToken cancellationToken = default) { ArraySegment <byte> drained = await ReadToEndAsync(utf8Json, cancellationToken).ConfigureAwait(false); try { return(Parse(drained.AsMemory(), readerOptions, drained.Array)); } catch { // Holds document content, clear it before returning it. drained.AsSpan().Clear(); ArrayPool <byte> .Shared.Return(drained.Array); throw; } }
/// <summary> /// Constructs a new <see cref="JsonReaderState"/> instance. /// </summary> /// <param name="maxDepth">Sets the maximum depth allowed when reading JSON, with the default set as 64. /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.</param> /// <param name="options">Defines the customized behavior of the <see cref="Utf8JsonReader"/> /// that is different from the JSON RFC (for example how to handle comments). /// By default, the <see cref="Utf8JsonReader"/> follows the JSON RFC strictly (i.e. comments within the JSON are invalid).</param> /// <exception cref="ArgumentException"> /// Thrown when the max depth is set to a non-positive value (<= 0) /// </exception> /// <remarks> /// An instance of this state must be passed to the <see cref="Utf8JsonReader"/> ctor with the JSON data. /// Unlike the <see cref="Utf8JsonReader"/>, which is a ref struct, the state can survive /// across async/await boundaries and hence this type is required to provide support for reading /// in more data asynchronously before continuing with a new instance of the <see cref="Utf8JsonReader"/>. /// </remarks> public JsonReaderState(int maxDepth = StackFreeMaxDepth, JsonReaderOptions options = default) { if (maxDepth <= 0) { throw ThrowHelper.GetArgumentException_MaxDepthMustBePositive(); } _stackFreeContainer = default; _lineNumber = default; _bytePositionInLine = default; _bytesConsumed = default; _currentDepth = default; _maxDepth = maxDepth; _inObject = default; _isNotPrimitive = default; _tokenType = default; _previousTokenType = default; _readerOptions = options; // Only allocate the stack if the user reads a JSON payload beyond the depth that the _stackFreeContainer can handle. // This way we avoid allocations in the common, default cases, and allocate lazily. _stack = null; }
private static JsonDocument ParseUnrented( ReadOnlyMemory <byte> utf8Json, JsonReaderOptions readerOptions, JsonTokenType tokenType = JsonTokenType.None) { // These tokens should already have been processed. Debug.Assert( tokenType != JsonTokenType.Null && tokenType != JsonTokenType.False && tokenType != JsonTokenType.True); ReadOnlySpan <byte> utf8JsonSpan = utf8Json.Span; MetadataDb database; if (tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number) { // For primitive types, we can avoid renting MetadataDb and creating StackRowStack. database = MetadataDb.CreateLocked(utf8Json.Length); StackRowStack stack = default; Parse(utf8JsonSpan, readerOptions, ref database, ref stack); } else { database = MetadataDb.CreateRented(utf8Json.Length, convertToAlloc: true); var stack = new StackRowStack(JsonDocumentOptions.DefaultMaxDepth * StackRow.Size); try { Parse(utf8JsonSpan, readerOptions, ref database, ref stack); } finally { stack.Dispose(); } } return(new JsonDocument(utf8Json, database)); }
/// <summary> /// Parse a <see cref="Stream"/> as UTF-8-encoded data representing a single JSON value into a /// JsonDocument. The Stream will be read to completion. /// </summary> /// <param name="utf8Json">JSON data to parse.</param> /// <param name="readerOptions">Options to control the reader behavior during parsing.</param> /// <returns> /// A JsonDocument representation of the JSON value. /// </returns> /// <exception cref="JsonReaderException"> /// <paramref name="utf8Json"/> does not represent a valid single JSON value. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="readerOptions"/> contains unsupported options. /// </exception> public static JsonDocument Parse(Stream utf8Json, JsonReaderOptions readerOptions = default) { if (utf8Json == null) { throw new ArgumentNullException(nameof(utf8Json)); } CheckSupportedOptions(readerOptions); ArraySegment <byte> drained = ReadToEnd(utf8Json); try { return(Parse(drained.AsMemory(), readerOptions, drained.Array)); } catch { // Holds document content, clear it before returning it. drained.AsSpan().Clear(); ArrayPool <byte> .Shared.Return(drained.Array); throw; } }
public static void AssertWithSingleAndMultiSegmentReader(string json, Utf8JsonReaderAction action, JsonReaderOptions options = default) { byte[] utf8 = Encoding.UTF8.GetBytes(json); var singleSegmentReader = new Utf8JsonReader(utf8, options); action(ref singleSegmentReader); ReadOnlySequence <byte> sequence = GetSequence(utf8, segmentSize: 1); var multiSegmentReader = new Utf8JsonReader(sequence, options); action(ref multiSegmentReader); }
private static TValue?ReadUsingMetadata <TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo) { ReadStack state = default; state.Initialize(jsonTypeInfo); JsonReaderState readerState = reader.CurrentState; if (readerState.Options.CommentHandling == JsonCommentHandling.Allow) { throw new ArgumentException(SR.JsonSerializerDoesNotSupportComments, nameof(reader)); } // Value copy to overwrite the ref on an exception and undo the destructive reads. Utf8JsonReader restore = reader; ReadOnlySpan <byte> valueSpan = default; ReadOnlySequence <byte> valueSequence = default; try { switch (reader.TokenType) { // A new reader was created and has never been read, // so we need to move to the first token. // (or a reader has terminated and we're about to throw) case JsonTokenType.None: // Using a reader loop the caller has identified a property they wish to // hydrate into a JsonDocument. Move to the value first. case JsonTokenType.PropertyName: { if (!reader.Read()) { ThrowHelper.ThrowJsonReaderException(ref reader, ExceptionResource.ExpectedOneCompleteToken); } break; } } switch (reader.TokenType) { // 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; if (sequence.IsEmpty) { valueSpan = reader.OriginalSpan.Slice( checked ((int)startingOffset), checked ((int)totalLength)); } else { valueSequence = sequence.Slice(startingOffset, totalLength); } Debug.Assert( reader.TokenType == JsonTokenType.EndObject || reader.TokenType == JsonTokenType.EndArray); break; } // Single-token values case JsonTokenType.Number: case JsonTokenType.True: case JsonTokenType.False: case JsonTokenType.Null: { if (reader.HasValueSequence) { valueSequence = reader.ValueSequence; } else { valueSpan = reader.ValueSpan; } break; } // String's ValueSequence/ValueSpan omits the quotes, we need them back. case JsonTokenType.String: { ReadOnlySequence <byte> sequence = reader.OriginalSequence; 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; 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]}"); 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]}"); } break; } default: { byte displayByte; if (reader.HasValueSequence) { displayByte = reader.ValueSequence.First.Span[0]; } else { displayByte = reader.ValueSpan[0]; } ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedStartOfValueNotFound, displayByte); break; } } } catch (JsonReaderException ex) { reader = restore; // Re-throw with Path information. ThrowHelper.ReThrowWithPath(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); JsonConverter jsonConverter = state.Current.JsonPropertyInfo !.ConverterBase; TValue? value = ReadCore <TValue>(jsonConverter, ref newReader, jsonTypeInfo.Options, 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); } }
/// <summary> /// Parse memory as UTF-8-encoded text representing a single JSON value into a JsonDocument. /// </summary> /// <remarks> /// <para> /// The <see cref="ReadOnlyMemory{T}"/> value will be used for the entire lifetime of the /// JsonDocument object, and the caller must ensure that the data therein does not change during /// the object lifetime. /// </para> /// /// <para> /// Because the input is considered to be text, a UTF-8 Byte-Order-Mark (BOM) must not be present. /// </para> /// </remarks> /// <param name="utf8Json">JSON text to parse.</param> /// <param name="readerOptions">Options to control the reader behavior during parsing.</param> /// <returns> /// A JsonDocument representation of the JSON value. /// </returns> /// <exception cref="JsonReaderException"> /// <paramref name="utf8Json"/> does not represent a valid single JSON value. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="readerOptions"/> contains unsupported options. /// </exception> public static JsonDocument Parse(ReadOnlyMemory <byte> utf8Json, JsonReaderOptions readerOptions = default) { CheckSupportedOptions(readerOptions); return(Parse(utf8Json, readerOptions, null)); }