| File: System\Text\Json\Reader\JsonReaderHelper.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Buffers.Text; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace System.Text.Json { internal static partial class JsonReaderHelper { // Characters that require the bracketed property name syntax in JSON Path. private static readonly SearchValues<char> s_specialCharacters = SearchValues.Create("$. '/\"[]()\t\n\r\f\b\\\u0085\u2028\u2029"); // Characters that need to be escaped in the single-quoted bracket notation. private static readonly SearchValues<char> s_charactersToEscape = SearchValues.Create("'\\"); public static bool ContainsSpecialCharacters(this ReadOnlySpan<char> text) => text.ContainsAny(s_specialCharacters); /// <summary> /// Appends a property name escaped for use in JSON Path single-quoted bracket notation. /// Escapes single quotes as \' and backslashes as \\. /// </summary> public static void AppendEscapedPropertyName(this ref ValueStringBuilder builder, string propertyName) { ReadOnlySpan<char> span = propertyName.AsSpan(); int i = span.IndexOfAny(s_charactersToEscape); // Fast path: if no characters need escaping, append directly. if (i < 0) { builder.Append(propertyName); return; } // Append the portion before the first character needing escaping. if (i > 0) { builder.Append(span.Slice(0, i)); } // Escape characters from position i onward. for (; i < span.Length; i++) { char c = span[i]; if (c is '\\' or '\'') { builder.Append('\\'); } builder.Append(c); } } /// <summary> /// Appends a property name escaped for use in JSON Path single-quoted bracket notation. /// Escapes single quotes as \' and backslashes as \\. /// </summary> public static void AppendEscapedPropertyName(this StringBuilder builder, string propertyName) { ReadOnlySpan<char> span = propertyName.AsSpan(); int i = span.IndexOfAny(s_charactersToEscape); // Fast path: if no characters need escaping, append directly. if (i < 0) { builder.Append(propertyName); return; } // Append the portion before the first character needing escaping. if (i > 0) { builder.Append(span.Slice(0, i)); } // Escape characters from position i onward. for (; i < span.Length; i++) { char c = span[i]; if (c is '\\' or '\'') { builder.Append('\\'); } builder.Append(c); } } public static (int, int) CountNewLines(ReadOnlySpan<byte> data) { int lastLineFeedIndex = data.LastIndexOf(JsonConstants.LineFeed); int newLines = 0; if (lastLineFeedIndex >= 0) { newLines = 1; data = data.Slice(0, lastLineFeedIndex); #if NET newLines += data.Count(JsonConstants.LineFeed); #else int pos; while ((pos = data.IndexOf(JsonConstants.LineFeed)) >= 0) { newLines++; data = data.Slice(pos + 1); } #endif } return (newLines, lastLineFeedIndex); } internal static JsonValueKind ToValueKind(this JsonTokenType tokenType) { switch (tokenType) { case JsonTokenType.None: return JsonValueKind.Undefined; case JsonTokenType.StartArray: return JsonValueKind.Array; case JsonTokenType.StartObject: return JsonValueKind.Object; case JsonTokenType.String: case JsonTokenType.Number: case JsonTokenType.True: case JsonTokenType.False: case JsonTokenType.Null: // This is the offset between the set of literals within JsonValueType and JsonTokenType // Essentially: JsonTokenType.Null - JsonValueType.Null return (JsonValueKind)((byte)tokenType - 4); default: Debug.Fail($"No mapping for token type {tokenType}"); return JsonValueKind.Undefined; } } // Returns true if the TokenType is a primitive "value", i.e. String, Number, True, False, and Null // Otherwise, return false. public static bool IsTokenTypePrimitive(JsonTokenType tokenType) => (tokenType - JsonTokenType.String) <= (JsonTokenType.Null - JsonTokenType.String); // A hex digit is valid if it is in the range: [0..9] | [A..F] | [a..f] // Otherwise, return false. public static bool IsHexDigit(byte nextByte) => HexConverter.IsHexChar(nextByte); public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out DateTime value) { if (!JsonHelpers.IsValidDateTimeOffsetParseLength(segment.Length)) { value = default; return false; } // Segment needs to be unescaped if (isEscaped) { return TryGetEscapedDateTime(segment, out value); } Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1); if (JsonHelpers.TryParseAsISO(segment, out DateTime tmp)) { value = tmp; return true; } value = default; return false; } public static unsafe bool TryGetEscapedDateTime(ReadOnlySpan<byte> source, out DateTime value) { Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength); Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength]; Unescape(source, sourceUnescaped, out int written); Debug.Assert(written > 0); sourceUnescaped = sourceUnescaped.Slice(0, written); Debug.Assert(!sourceUnescaped.IsEmpty); if (JsonHelpers.IsValidUnescapedDateTimeOffsetParseLength(sourceUnescaped.Length) && JsonHelpers.TryParseAsISO(sourceUnescaped, out DateTime tmp)) { value = tmp; return true; } value = default; return false; } public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out DateTimeOffset value) { if (!JsonHelpers.IsValidDateTimeOffsetParseLength(segment.Length)) { value = default; return false; } // Segment needs to be unescaped if (isEscaped) { return TryGetEscapedDateTimeOffset(segment, out value); } Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1); if (JsonHelpers.TryParseAsISO(segment, out DateTimeOffset tmp)) { value = tmp; return true; } value = default; return false; } public static unsafe bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out DateTimeOffset value) { Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength); Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength]; Unescape(source, sourceUnescaped, out int written); Debug.Assert(written > 0); sourceUnescaped = sourceUnescaped.Slice(0, written); Debug.Assert(!sourceUnescaped.IsEmpty); if (JsonHelpers.IsValidUnescapedDateTimeOffsetParseLength(sourceUnescaped.Length) && JsonHelpers.TryParseAsISO(sourceUnescaped, out DateTimeOffset tmp)) { value = tmp; return true; } value = default; return false; } public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out Guid value) { if (segment.Length > JsonConstants.MaximumEscapedGuidLength) { value = default; return false; } // Segment needs to be unescaped if (isEscaped) { return TryGetEscapedGuid(segment, out value); } Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1); if (segment.Length == JsonConstants.MaximumFormatGuidLength && Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D')) { value = tmp; return true; } value = default; return false; } public static unsafe bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value) { Debug.Assert(source.Length <= JsonConstants.MaximumEscapedGuidLength); Span<byte> utf8Unescaped = stackalloc byte[JsonConstants.MaximumEscapedGuidLength]; Unescape(source, utf8Unescaped, out int written); Debug.Assert(written > 0); utf8Unescaped = utf8Unescaped.Slice(0, written); Debug.Assert(!utf8Unescaped.IsEmpty); if (utf8Unescaped.Length == JsonConstants.MaximumFormatGuidLength && Utf8Parser.TryParse(utf8Unescaped, out Guid tmp, out _, 'D')) { value = tmp; return true; } value = default; return false; } #if NET public static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out Half value) { if (span.Length == 3) { if (span.SequenceEqual(JsonConstants.NaNValue)) { value = Half.NaN; return true; } } else if (span.Length == 8) { if (span.SequenceEqual(JsonConstants.PositiveInfinityValue)) { value = Half.PositiveInfinity; return true; } } else if (span.Length == 9) { if (span.SequenceEqual(JsonConstants.NegativeInfinityValue)) { value = Half.NegativeInfinity; return true; } } value = default; return false; } #endif public static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out float value) { if (span.Length == 3) { if (span.SequenceEqual(JsonConstants.NaNValue)) { value = float.NaN; return true; } } else if (span.Length == 8) { if (span.SequenceEqual(JsonConstants.PositiveInfinityValue)) { value = float.PositiveInfinity; return true; } } else if (span.Length == 9) { if (span.SequenceEqual(JsonConstants.NegativeInfinityValue)) { value = float.NegativeInfinity; return true; } } value = 0; return false; } public static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out double value) { if (span.Length == 3) { if (span.SequenceEqual(JsonConstants.NaNValue)) { value = double.NaN; return true; } } else if (span.Length == 8) { if (span.SequenceEqual(JsonConstants.PositiveInfinityValue)) { value = double.PositiveInfinity; return true; } } else if (span.Length == 9) { if (span.SequenceEqual(JsonConstants.NegativeInfinityValue)) { value = double.NegativeInfinity; return true; } } value = 0; return false; } } }