// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Diagnostics; using System.Text.Json; #pragma warning disable CA1045 // Do not pass types by reference namespace EpicGames.Core { /// /// Utility methods for utf8 json reader/writer /// public static class JsonExtensions { /// /// Tries to read a property name then move to the value token /// /// Token reader /// Receives the property name on success /// True if the read succeeded public static bool TryReadNextPropertyName(scoped ref Utf8JsonReader reader, out ReadOnlySpan propertyName) { if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName) { propertyName = reader.ValueSpan; return reader.Read(); } else { propertyName = []; return false; } } /// /// Reads a Utf8 string from the current json token /// /// /// public static ReadOnlySpan GetUtf8String(this Utf8JsonReader reader) { ReadOnlySpan span = reader.ValueSpan; // Scan for the first backslash for (int firstIdx = 0; firstIdx < span.Length; firstIdx++) { if (span[firstIdx] == '\\') { // Compute the length of the unescaped string int length = span.Length; for(int idx = firstIdx; idx < span.Length; idx++) { if (span[idx] == '\\') { switch (span[idx + 1]) { case (byte)'"': case (byte)'\\': case (byte)'/': case (byte)'b': case (byte)'f': case (byte)'n': case (byte)'r': case (byte)'t': idx += 2 - 1; length--; break; case (byte)'u': int c = (StringUtils.GetHexDigit(span[idx + 2]) << 12) + (StringUtils.GetHexDigit(span[idx + 3]) << 8) + (StringUtils.GetHexDigit(span[idx + 4]) << 4) + StringUtils.GetHexDigit(span[idx + 5]); idx += 6 - 1; length += GetUtf8ByteCount(c) - 6; break; } } } // Write the new string byte[] result = new byte[length]; span.Slice(0, firstIdx).CopyTo(result); // Trim the output spans ReadOnlySpan input = span.Slice(firstIdx); Span output = result.AsSpan(firstIdx); // Compute the length of the unescaped string for(; ;) { switch (input[1]) { case (byte)'"': input = input.Slice(2); output[0] = (byte)'\"'; output = output.Slice(1); break; case (byte)'\\': input = input.Slice(2); output[0] = (byte)'\\'; output = output.Slice(1); break; case (byte)'/': input = input.Slice(2); output[0] = (byte)'/'; output = output.Slice(1); break; case (byte)'b': input = input.Slice(2); output[0] = (byte)'\b'; output = output.Slice(1); break; case (byte)'f': input = input.Slice(2); output[0] = (byte)'\f'; output = output.Slice(1); break; case (byte)'n': input = input.Slice(2); output[0] = (byte)'\n'; output = output.Slice(1); break; case (byte)'r': input = input.Slice(2); output[0] = (byte)'\r'; output = output.Slice(1); break; case (byte)'t': input = input.Slice(2); output[0] = (byte)'\t'; output = output.Slice(1); break; case (byte)'u': int c = (StringUtils.GetHexDigit(input[2]) << 12) + (StringUtils.GetHexDigit(input[3]) << 8) + (StringUtils.GetHexDigit(input[4]) << 4) + StringUtils.GetHexDigit(input[5]); input = input.Slice(6); int len = GetUtf8Bytes(c, output); output = output.Slice(len); break; } int escapeIdx = input.IndexOf((byte)'\\'); if (escapeIdx == -1) { Debug.Assert(input.Length == output.Length); input.CopyTo(output); return result; } input.Slice(0, escapeIdx).CopyTo(output); input = input.Slice(escapeIdx); output = output.Slice(escapeIdx); } } } return span; } static int GetUtf8Bytes(int c, Span output) { if (c <= 0x7f) { output[0] = (byte)c; return 1; } else if (c <= 0x7ff) { output[0] = (byte)(0b11000000 | (c >> 6)); output[1] = (byte)(0b10000000 | (c & 0x1f)); return 2; } else if (c <= 0xffff) { output[0] = (byte)(0b11100000 | (c >> 12)); output[1] = (byte)(0b10000000 | ((c >> 6) & 0x1f)); output[2] = (byte)(0b10000000 | (c & 0x1f)); return 3; } else { output[0] = (byte)(0b11110000 | (c >> 18)); output[1] = (byte)(0b10000000 | ((c >> 12) & 0x1f)); output[2] = (byte)(0b10000000 | ((c >> 6) & 0x1f)); output[3] = (byte)(0b10000000 | (c & 0x1f)); return 4; } } static int GetUtf8ByteCount(int c) { if (c <= 0x7f) { return 1; } else if (c <= 0x7ff) { return 2; } else if (c <= 0xffff) { return 3; } else { return 4; } } } }