Exemple #1
0
        /// <summary>
        /// Parses the current JSON token value from the source, unescaped, and transcoded as a <see cref="string"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if trying to get the value of the JSON token that is not a string
        /// (i.e. other than <see cref="JsonTokenType.String"/> or <see cref="JsonTokenType.PropertyName"/>).
        /// <seealso cref="TokenType" />
        /// It will also throw when the JSON string contains invalid UTF-8 bytes, or invalid UTF-16 surrogates.
        /// </exception>
        public string GetString()
        {
            if (TokenType != JsonTokenType.String && TokenType != JsonTokenType.PropertyName)
            {
                throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
            }

            ReadOnlySpan <byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;

            if (_stringHasEscaping)
            {
                int idx = span.IndexOf(JsonConstants.BackSlash);
                Debug.Assert(idx != -1);
                return(JsonReaderHelper.GetUnescapedString(span, idx));
            }

            Debug.Assert(span.IndexOf(JsonConstants.BackSlash) == -1);
            return(JsonReaderHelper.TranscodeHelper(span));
        }
        private static TValue?Deserialize <TValue>(string json, Type returnType, JsonSerializerOptions?options)
        {
            const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024;

            if (options == null)
            {
                options = JsonSerializerOptions.s_defaultOptions;
            }

            byte[]? tempArray = null;

            // For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
            Span <byte> utf8 = json.Length <= (ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ?
                               // Use a pooled alloc.
                               tempArray = ArrayPool <byte> .Shared.Rent(json.Length *JsonConstants.MaxExpansionFactorWhileTranscoding) :
                               // Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation)
                               // and by using a normal alloc we can avoid the Clear().
                                           new byte[JsonReaderHelper.GetUtf8ByteCount(json.AsSpan())];

            try
            {
                int actualByteCount = JsonReaderHelper.GetUtf8FromText(json.AsSpan(), utf8);
                utf8 = utf8.Slice(0, actualByteCount);

                var readerState = new JsonReaderState(options.GetReaderOptions());
                var reader      = new Utf8JsonReader(utf8, isFinalBlock: true, readerState);

                TValue value = ReadCore <TValue>(ref reader, returnType, options);

                // The reader should have thrown if we have remaining bytes.
                Debug.Assert(reader.BytesConsumed == actualByteCount);

                return(value);
            }
            finally
            {
                if (tempArray != null)
                {
                    utf8.Clear();
                    ArrayPool <byte> .Shared.Return(tempArray);
                }
            }
        }
        /// <summary>
        /// Parses the current JSON token value from the source and decodes the base 64 encoded JSON string as bytes.
        /// Returns <see langword="true"/> if the entire token value is encoded as valid base 64 text and can be successfully
        /// decoded to bytes.
        /// Returns <see langword="false"/> otherwise.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.String"/>.
        /// <seealso cref="TokenType" />
        /// </exception>
        public bool TryGetBytesFromBase64(out byte[] value)
        {
            if (TokenType != JsonTokenType.String)
            {
                throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
            }

            ReadOnlySpan <byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;

            if (_stringHasEscaping)
            {
                int idx = span.IndexOf(JsonConstants.BackSlash);
                Debug.Assert(idx != -1);
                return(JsonReaderHelper.TryGetUnescapedBase64Bytes(span, idx, out value));
            }

            Debug.Assert(span.IndexOf(JsonConstants.BackSlash) == -1);
            return(JsonReaderHelper.TryDecodeBase64(span, out value));
        }
Exemple #4
0
        internal double GetDoubleWithQuotes()
        {
            ReadOnlySpan <byte> span = GetUnescapedSpan();

            if (JsonReaderHelper.TryGetFloatingPointConstant(span, out double value))
            {
                return(value);
            }

            // NETCOREAPP implementation of the TryParse method above permits case-insensitive variants of the
            // float constants "NaN", "Infinity", "-Infinity". This differs from the NETFRAMEWORK implementation.
            // The following logic reconciles the two implementations to enforce consistent behavior.
            if (!(Utf8Parser.TryParse(span, out value, out int bytesConsumed) &&
                  span.Length == bytesConsumed &&
                  JsonHelpers.IsFinite(value)))
            {
                ThrowHelper.ThrowFormatException(NumericType.Double);
            }

            return(value);
        }
        /// <summary>
        ///   Parse text representing a single JSON value into a JsonDocument.
        /// </summary>
        /// <remarks>
        ///   The <see cref="ReadOnlyMemory{T}"/> value may 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.
        /// </remarks>
        /// <param name="json">JSON text to parse.</param>
        /// <param name="options">Options to control the reader behavior during parsing.</param>
        /// <returns>
        ///   A JsonDocument representation of the JSON value.
        /// </returns>
        /// <exception cref="JsonException">
        ///   <paramref name="json"/> does not represent a valid single JSON value.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="options"/> contains unsupported options.
        /// </exception>
        public static JsonDocument Parse(ReadOnlyMemory <char> json, JsonDocumentOptions options = default)
        {
            ReadOnlySpan <char> jsonChars = json.Span;
            int expectedByteCount         = JsonReaderHelper.GetUtf8ByteCount(jsonChars);

            byte[] utf8Bytes = ArrayPool <byte> .Shared.Rent(expectedByteCount);

            try
            {
                int actualByteCount = JsonReaderHelper.GetUtf8FromText(jsonChars, utf8Bytes);
                Debug.Assert(expectedByteCount == actualByteCount);

                return(Parse(utf8Bytes.AsMemory(0, actualByteCount), options.GetReaderOptions(), utf8Bytes));
            }
            catch
            {
                // Holds document content, clear it before returning it.
                utf8Bytes.AsSpan(0, expectedByteCount).Clear();
                ArrayPool <byte> .Shared.Return(utf8Bytes);

                throw;
            }
        }
Exemple #6
0
        private static ReadOnlySpan <byte> GetUnescapedString(ReadOnlySpan <byte> utf8Source, int idx)
        {
            // The escaped name is always longer than the unescaped, so it is safe to use escaped name for the buffer length.
            int length = utf8Source.Length;

            byte[]? pooledName = null;

            Span <byte> unescapedName = length <= JsonConstants.StackallocThreshold ?
                                        stackalloc byte[length] :
                                        (pooledName = ArrayPool <byte> .Shared.Rent(length));

            JsonReaderHelper.Unescape(utf8Source, unescapedName, idx, out int written);
            ReadOnlySpan <byte> propertyName = unescapedName.Slice(0, written).ToArray();

            if (pooledName != null)
            {
                // We clear the array because it is "user data" (although a property name).
                new Span <byte>(pooledName, 0, written).Clear();
                ArrayPool <byte> .Shared.Return(pooledName);
            }

            return(propertyName);
        }
Exemple #7
0
        private static JsonEncodedText TranscodeAndEncode(ReadOnlySpan <char> value)
        {
            JsonWriterHelper.ValidateValue(value);

            int expectedByteCount = JsonReaderHelper.GetUtf8ByteCount(value);

            byte[] utf8Bytes = ArrayPool <byte> .Shared.Rent(expectedByteCount);

            JsonEncodedText encodedText;

            // Since GetUtf8ByteCount above already throws on invalid input, the transcoding
            // to UTF-8 is guaranteed to succeed here. Therefore, there's no need for a try-catch-finally block.
            int actualByteCount = JsonReaderHelper.GetUtf8FromText(value, utf8Bytes);

            Debug.Assert(expectedByteCount == actualByteCount);

            encodedText = EncodeHelper(utf8Bytes.AsSpan(0, actualByteCount));

            // On the basis that this is user data, go ahead and clear it.
            utf8Bytes.AsSpan(0, expectedByteCount).Clear();
            ArrayPool <byte> .Shared.Return(utf8Bytes);

            return(encodedText);
        }
        private bool TryGetNamedPropertyValue(
            int startIndex,
            int endIndex,
            ReadOnlySpan <byte> propertyName,
            out JsonElement value)
        {
            ReadOnlySpan <byte> documentSpan = _utf8Json.Span;

            // Move to the row before the EndObject
            int index = endIndex - DbRow.Size;

            while (index > startIndex)
            {
                DbRow row = _parsedData.Get(index);
                Debug.Assert(row.TokenType != JsonTokenType.PropertyName);

                // Move before the value
                if (row.IsSimpleValue)
                {
                    index -= DbRow.Size;
                }
                else
                {
                    Debug.Assert(row.NumberOfRows > 0);
                    index -= DbRow.Size * (row.NumberOfRows + 1);
                }

                row = _parsedData.Get(index);
                Debug.Assert(row.TokenType == JsonTokenType.PropertyName);

                ReadOnlySpan <byte> currentPropertyName = documentSpan.Slice(row.Location, row.SizeOrLength);

                if (row.HasComplexChildren)
                {
                    // An escaped property name will be longer than an unescaped candidate, so only unescape
                    // when the lengths are compatible.
                    if (currentPropertyName.Length > propertyName.Length)
                    {
                        int idx = currentPropertyName.IndexOf(JsonConstants.BackSlash);
                        Debug.Assert(idx >= 0);

                        // If everything up to where the property name has a backslash matches, keep going.
                        if (propertyName.Length > idx &&
                            currentPropertyName.Slice(0, idx).SequenceEqual(propertyName.Slice(0, idx)))
                        {
                            int    remaining = currentPropertyName.Length - idx;
                            int    written   = 0;
                            byte[] rented    = null;

                            try
                            {
                                Span <byte> utf8Unescaped = remaining <= JsonConstants.StackallocThreshold ?
                                                            stackalloc byte[remaining] :
                                                            (rented = ArrayPool <byte> .Shared.Rent(remaining));

                                // Only unescape the part we haven't processed.
                                JsonReaderHelper.Unescape(currentPropertyName.Slice(idx), utf8Unescaped, 0, out written);

                                // If the unescaped remainder matches the input remainder, it's a match.
                                if (utf8Unescaped.Slice(0, written).SequenceEqual(propertyName.Slice(idx)))
                                {
                                    // If the property name is a match, the answer is the next element.
                                    value = new JsonElement(this, index + DbRow.Size);
                                    return(true);
                                }
                            }
                            finally
                            {
                                if (rented != null)
                                {
                                    rented.AsSpan(0, written).Clear();
                                    ArrayPool <byte> .Shared.Return(rented);
                                }
                            }
                        }
                    }
                }
                else if (currentPropertyName.SequenceEqual(propertyName))
                {
                    // If the property name is a match, the answer is the next element.
                    value = new JsonElement(this, index + DbRow.Size);
                    return(true);
                }

                // Move to the previous value
                index -= DbRow.Size;
            }

            value = default;
            return(false);
        }
        internal bool TryGetNamedPropertyValue(int index, ReadOnlySpan <char> propertyName, out JsonElement value)
        {
            CheckNotDisposed();

            DbRow row = _parsedData.Get(index);

            CheckExpectedType(JsonTokenType.StartObject, row.TokenType);

            // Only one row means it was EndObject.
            if (row.NumberOfRows == 1)
            {
                value = default;
                return(false);
            }

            int maxBytes   = JsonReaderHelper.s_utf8Encoding.GetMaxByteCount(propertyName.Length);
            int startIndex = index + DbRow.Size;
            int endIndex   = checked (row.NumberOfRows * DbRow.Size + index);

            if (maxBytes < JsonConstants.StackallocThreshold)
            {
                Span <byte> utf8Name = stackalloc byte[JsonConstants.StackallocThreshold];
                int         len      = JsonReaderHelper.GetUtf8FromText(propertyName, utf8Name);
                utf8Name = utf8Name.Slice(0, len);

                return(TryGetNamedPropertyValue(
                           startIndex,
                           endIndex,
                           utf8Name,
                           out value));
            }

            // Unescaping the property name will make the string shorter (or the same)
            // So the first viable candidate is one whose length in bytes matches, or
            // exceeds, our length in chars.
            //
            // The maximal escaping seems to be 6 -> 1 ("\u0030" => "0"), but just transcode
            // and switch once one viable long property is found.

            int minBytes = propertyName.Length;
            // Move to the row before the EndObject
            int candidateIndex = endIndex - DbRow.Size;

            while (candidateIndex > index)
            {
                int passedIndex = candidateIndex;

                row = _parsedData.Get(candidateIndex);
                Debug.Assert(row.TokenType != JsonTokenType.PropertyName);

                // Move before the value
                if (row.IsSimpleValue)
                {
                    candidateIndex -= DbRow.Size;
                }
                else
                {
                    Debug.Assert(row.NumberOfRows > 0);
                    candidateIndex -= DbRow.Size * (row.NumberOfRows + 1);
                }

                row = _parsedData.Get(candidateIndex);
                Debug.Assert(row.TokenType == JsonTokenType.PropertyName);

                if (row.SizeOrLength >= minBytes)
                {
                    byte[] tmpUtf8 = ArrayPool <byte> .Shared.Rent(maxBytes);

                    Span <byte> utf8Name = default;

                    try
                    {
                        int len = JsonReaderHelper.GetUtf8FromText(propertyName, tmpUtf8);
                        utf8Name = tmpUtf8.AsSpan(0, len);

                        return(TryGetNamedPropertyValue(
                                   startIndex,
                                   passedIndex + DbRow.Size,
                                   utf8Name,
                                   out value));
                    }
                    finally
                    {
                        // While property names aren't usually a secret, they also usually
                        // aren't long enough to end up in the rented buffer transcode path.
                        //
                        // On the basis that this is user data, go ahead and clear it.
                        utf8Name.Clear();
                        ArrayPool <byte> .Shared.Return(tmpUtf8);
                    }
                }

                // Move to the previous value
                candidateIndex -= DbRow.Size;
            }

            // None of the property names were within the range that the UTF-8 encoding would have been.
            value = default;
            return(false);
        }
Exemple #10
0
        /// <summary>
        /// Parse the text representing a single JSON value into a <paramref name="returnType"/>.
        /// </summary>
        /// <returns>A <paramref name="returnType"/> representation of the JSON value.</returns>
        /// <param name="json">JSON text to parse.</param>
        /// <param name="returnType">The type of the object to convert to and return.</param>
        /// <param name="options">Options to control the behavior during parsing.</param>
        /// <exception cref="System.ArgumentNullException">
        /// Thrown if <paramref name="json"/> or <paramref name="returnType"/> is null.
        /// </exception>
        /// <exception cref="JsonException">
        /// Thrown when the JSON is invalid,
        /// the <paramref name="returnType"/> is not compatible with the JSON,
        /// or when there is remaining data in the Stream.
        /// </exception>
        /// <remarks>Using a <see cref="string"/> is not as efficient as using the
        /// UTF-8 methods since the implementation natively uses UTF-8.
        /// </remarks>
        public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null)
        {
            if (json == null)
            {
                throw new ArgumentNullException(nameof(json));
            }

            if (returnType == null)
            {
                throw new ArgumentNullException(nameof(returnType));
            }

            if (options == null)
            {
                options = JsonSerializerOptions.s_defaultOptions;
            }

            object result;

            byte[] tempArray = null;

            int maxBytes;

            // For performance, avoid asking for the actual byte count unless necessary.
            if (json.Length > MaxArrayLengthBeforeCalculatingSize)
            {
                // Get the actual byte count in order to handle large input.
                maxBytes = JsonReaderHelper.GetUtf8ByteCount(json.AsSpan());
            }
            else
            {
                maxBytes = json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding;
            }

            Span <byte> utf8 = maxBytes <= JsonConstants.StackallocThreshold ?
                               stackalloc byte[maxBytes] :
                               (tempArray = ArrayPool <byte> .Shared.Rent(maxBytes));

            try
            {
                int actualByteCount = JsonReaderHelper.GetUtf8FromText(json.AsSpan(), utf8);
                utf8 = utf8.Slice(0, actualByteCount);

                var readerState = new JsonReaderState(options.GetReaderOptions());
                var reader      = new Utf8JsonReader(utf8, isFinalBlock: true, readerState);
                result = ReadCore(returnType, options, ref reader);

                if (reader.BytesConsumed != actualByteCount)
                {
                    ThrowHelper.ThrowJsonException_DeserializeDataRemaining(
                        actualByteCount, actualByteCount - reader.BytesConsumed);
                }
            }
            finally
            {
                if (tempArray != null)
                {
                    utf8.Clear();
                    ArrayPool <byte> .Shared.Return(tempArray);
                }
            }

            return(result);
        }
Exemple #11
0
        internal static JsonPropertyInfo LookupProperty(
            object obj,
            ref Utf8JsonReader reader,
            JsonSerializerOptions options,
            ref ReadStack state,
            out bool useExtensionProperty)
        {
            Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);

            JsonPropertyInfo jsonPropertyInfo;

            ReadOnlySpan <byte> unescapedPropertyName;
            ReadOnlySpan <byte> propertyName = reader.GetSpan();

            if (reader._stringHasEscaping)
            {
                int idx = propertyName.IndexOf(JsonConstants.BackSlash);
                Debug.Assert(idx != -1);
                unescapedPropertyName = JsonReaderHelper.GetUnescapedSpan(propertyName, idx);
            }
            else
            {
                unescapedPropertyName = propertyName;
            }

            if (options.ReferenceHandling.ShouldReadPreservedReferences())
            {
                if (propertyName.Length > 0 && propertyName[0] == '$')
                {
                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
                }
            }

            jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);

            // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called.
            state.Current.PropertyIndex++;

            // Determine if we should use the extension property.
            if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
            {
                JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
                if (dataExtProperty != null)
                {
                    state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
                    CreateDataExtensionProperty(obj, dataExtProperty);
                    jsonPropertyInfo = dataExtProperty;
                }

                state.Current.JsonPropertyInfo = jsonPropertyInfo;
                useExtensionProperty           = true;
                return(jsonPropertyInfo);
            }

            // Support JsonException.Path.
            Debug.Assert(
                jsonPropertyInfo.JsonPropertyName == null ||
                options.PropertyNameCaseInsensitive ||
                unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));

            state.Current.JsonPropertyInfo = jsonPropertyInfo;

            if (jsonPropertyInfo.JsonPropertyName == null)
            {
                byte[] propertyNameArray = unescapedPropertyName.ToArray();
                if (options.PropertyNameCaseInsensitive)
                {
                    // Each payload can have a different name here; remember the value on the temporary stack.
                    state.Current.JsonPropertyName = propertyNameArray;
                }
                else
                {
                    // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
                    // so it will match the incoming payload except when case insensitivity is enabled (which is handled above).
                    state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray;
                }
            }

            state.Current.JsonPropertyInfo = jsonPropertyInfo;
            useExtensionProperty           = false;
            return(jsonPropertyInfo);
        }
        private static void EscapeString(ReadOnlySpan <char> value, Span <char> destination, JavaScriptEncoder encoder, ref int written)
        {
            // todo: issue #39523: add an Encode(ReadOnlySpan<char>) decode API to System.Text.Encodings.Web.TextEncoding to avoid utf16->utf8->utf16 conversion.

            Debug.Assert(encoder != null);

            // Convert char to byte.
            byte[]      utf8DestinationArray = null;
            Span <byte> utf8Destination;
            int         length = checked ((value.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding);

            if (length > JsonConstants.StackallocThreshold)
            {
                utf8DestinationArray = ArrayPool <byte> .Shared.Rent(length);

                utf8Destination = utf8DestinationArray;
            }
            else
            {
                unsafe
                {
                    byte *ptr = stackalloc byte[JsonConstants.StackallocThreshold];
                    utf8Destination = new Span <byte>(ptr, JsonConstants.StackallocThreshold);
                }
            }

            ReadOnlySpan <byte> utf16Value   = MemoryMarshal.AsBytes(value);
            OperationStatus     toUtf8Status = ToUtf8(utf16Value, utf8Destination, out int bytesConsumed, out int bytesWritten);

            Debug.Assert(toUtf8Status != OperationStatus.DestinationTooSmall);
            Debug.Assert(toUtf8Status != OperationStatus.NeedMoreData);

            if (toUtf8Status != OperationStatus.Done)
            {
                if (utf8DestinationArray != null)
                {
                    utf8Destination.Slice(0, bytesWritten).Clear();
                    ArrayPool <byte> .Shared.Return(utf8DestinationArray);
                }

                ThrowHelper.ThrowArgumentException_InvalidUTF8(utf16Value.Slice(bytesWritten));
            }

            Debug.Assert(toUtf8Status == OperationStatus.Done);
            Debug.Assert(bytesConsumed == utf16Value.Length);

            // Escape the bytes.
            byte[]      utf8ConvertedDestinationArray = null;
            Span <byte> utf8ConvertedDestination;

            length = checked (bytesWritten * JsonConstants.MaxExpansionFactorWhileEscaping);
            if (length > JsonConstants.StackallocThreshold)
            {
                utf8ConvertedDestinationArray = ArrayPool <byte> .Shared.Rent(length);

                utf8ConvertedDestination = utf8ConvertedDestinationArray;
            }
            else
            {
                unsafe
                {
                    byte *ptr = stackalloc byte[JsonConstants.StackallocThreshold];
                    utf8ConvertedDestination = new Span <byte>(ptr, JsonConstants.StackallocThreshold);
                }
            }

            EscapeString(utf8Destination.Slice(0, bytesWritten), utf8ConvertedDestination, indexOfFirstByteToEscape: 0, encoder, out int convertedBytesWritten);

            if (utf8DestinationArray != null)
            {
                utf8Destination.Slice(0, bytesWritten).Clear();
                ArrayPool <byte> .Shared.Return(utf8DestinationArray);
            }

            // Convert byte to char.
#if BUILDING_INBOX_LIBRARY
            OperationStatus toUtf16Status = Utf8.ToUtf16(utf8ConvertedDestination.Slice(0, convertedBytesWritten), destination, out int bytesRead, out int charsWritten);
            Debug.Assert(toUtf16Status == OperationStatus.Done);
            Debug.Assert(bytesRead == convertedBytesWritten);
#else
            string utf16 = JsonReaderHelper.GetTextFromUtf8(utf8ConvertedDestination.Slice(0, convertedBytesWritten));
            utf16.AsSpan().CopyTo(destination);
            int charsWritten = utf16.Length;
#endif
            written += charsWritten;

            if (utf8ConvertedDestinationArray != null)
            {
                utf8ConvertedDestination.Slice(0, written).Clear();
                ArrayPool <byte> .Shared.Return(utf8ConvertedDestinationArray);
            }
        }