/// <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)); }
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; } }
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); }
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); }
/// <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); }
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); } }