예제 #1
0
        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;
            }
        }
예제 #2
0
        /// <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 (&lt;= 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;
        }
예제 #3
0
        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));
        }
예제 #4
0
        /// <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;
            }
        }
예제 #5
0
        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);
            }
        }
예제 #7
0
        /// <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));
        }