| File: ApiLifecycle\Json\JsonReader.cs | Web Access |
| Project: src\src\Analyzers\Microsoft.Analyzers.Local\Microsoft.Analyzers.Local.csproj (Microsoft.Analyzers.Local) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Forked from StyleCop.Analyzers repo. using System; using System.Globalization; using System.IO; using System.Text; namespace Microsoft.Extensions.LocalAnalyzers.Json; /// <summary> /// Represents a reader that can read JsonValues. /// </summary> internal sealed class JsonReader { private readonly TextScanner _scanner; private JsonReader(TextReader reader) { _scanner = new TextScanner(reader); } /// <summary> /// Creates a JsonValue by using the given TextReader. /// </summary> /// <param name="reader">The TextReader used to read a JSON message.</param> /// <returns>The parsed <see cref="JsonValue"/>.</returns> public static JsonValue Parse(TextReader reader) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } return new JsonReader(reader).Parse(); } /// <summary> /// Creates a JsonValue by reader the JSON message in the given string. /// </summary> /// <param name="source">The string containing the JSON message.</param> /// <returns>The parsed <see cref="JsonValue"/>.</returns> public static JsonValue Parse(string source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } using var reader = new StringReader(source); return Parse(reader); } private string ReadJsonKey() { return ReadString(); } private JsonValue ReadJsonValue() { _scanner.SkipWhitespace(); var next = _scanner.Peek(); if (char.IsNumber(next)) { return ReadNumber(); } return next switch { '{' => ReadObject(), '[' => ReadArray(), '"' => ReadString(), '-' => ReadNumber(), 't' or 'f' => ReadBoolean(), 'n' => ReadNull(), _ => throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, _scanner.Position), }; } private JsonValue ReadNull() { _scanner.Assert("null"); return JsonValue.Null; } private JsonValue ReadBoolean() { switch (_scanner.Peek()) { case 't': _scanner.Assert("true"); return true; default: _scanner.Assert("false"); return false; } } private void ReadDigits(StringBuilder builder) { while (true) { int next = _scanner.Peek(throwAtEndOfFile: false); if (next == -1 || !char.IsNumber((char)next)) { return; } _ = builder.Append(_scanner.Read()); } } private JsonValue ReadNumber() { var builder = new StringBuilder(); if (_scanner.Peek() == '-') { _ = builder.Append(_scanner.Read()); } if (_scanner.Peek() == '0') { _ = builder.Append(_scanner.Read()); } else { ReadDigits(builder); } if (_scanner.Peek(throwAtEndOfFile: false) == '.') { _ = builder.Append(_scanner.Read()); ReadDigits(builder); } if (_scanner.Peek(throwAtEndOfFile: false) == 'e' || _scanner.Peek(throwAtEndOfFile: false) == 'E') { _ = builder.Append(_scanner.Read()); var next = _scanner.Peek(); switch (next) { case '+': case '-': _ = builder.Append(_scanner.Read()); break; } ReadDigits(builder); } return double.Parse( builder.ToString(), CultureInfo.InvariantCulture); } private string ReadString() { var builder = new StringBuilder(); _scanner.Assert('"'); while (true) { var errorPosition = _scanner.Position; var c = _scanner.Read(); if (c == '\\') { errorPosition = _scanner.Position; c = _scanner.Read(); _ = char.ToLowerInvariant(c) switch { '"' or '\\' or '/' => builder.Append(c), 'b' => builder.Append('\b'), 'f' => builder.Append('\f'), 'n' => builder.Append('\n'), 'r' => builder.Append('\r'), 't' => builder.Append('\t'), 'u' => builder.Append(ReadUnicodeLiteral()), _ => throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, errorPosition), }; } else if (c == '"') { break; } else { if (char.IsControl(c)) { throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, errorPosition); } _ = builder.Append(c); } } return builder.ToString(); } private int ReadHexDigit() { var errorPosition = _scanner.Position; #pragma warning disable S109 // Magic numbers should not be used return char.ToUpperInvariant(_scanner.Read()) switch { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, _ => throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, errorPosition), }; } private char ReadUnicodeLiteral() { int value = 0; value += ReadHexDigit() * 4096; // 16^3 value += ReadHexDigit() * 256; // 16^2 value += ReadHexDigit() * 16; // 16^1 value += ReadHexDigit(); // 16^0 return (char)value; } #pragma warning restore S109 // Magic numbers should not be used private JsonObject ReadObject() { return ReadObject([]); } private JsonObject ReadObject(JsonObject jsonObject) { _scanner.Assert('{'); _scanner.SkipWhitespace(); if (_scanner.Peek() == '}') { _ = _scanner.Read(); } else { while (true) { _scanner.SkipWhitespace(); var errorPosition = _scanner.Position; var key = ReadJsonKey(); if (jsonObject.ContainsKey(key)) { throw new JsonParseException( ParsingError.DuplicateObjectKeys, errorPosition); } _scanner.SkipWhitespace(); _scanner.Assert(':'); _scanner.SkipWhitespace(); var value = ReadJsonValue(); _ = jsonObject.Add(key, value); _scanner.SkipWhitespace(); errorPosition = _scanner.Position; var next = _scanner.Read(); if (next == ',') { // Allow trailing commas in objects _scanner.SkipWhitespace(); if (_scanner.Peek() == '}') { next = _scanner.Read(); } } if (next == '}') { break; } else if (next != ',') { throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, errorPosition); } } } return jsonObject; } private JsonArray ReadArray() { return ReadArray([]); } private JsonArray ReadArray(JsonArray jsonArray) { _scanner.Assert('['); _scanner.SkipWhitespace(); if (_scanner.Peek() == ']') { _ = _scanner.Read(); } else { while (true) { _scanner.SkipWhitespace(); var value = ReadJsonValue(); _ = jsonArray.Add(value); _scanner.SkipWhitespace(); var errorPosition = _scanner.Position; var next = _scanner.Read(); if (next == ',') { // Allow trailing commas in arrays _scanner.SkipWhitespace(); if (_scanner.Peek() == ']') { next = _scanner.Read(); } } if (next == ']') { break; } else if (next != ',') { throw new JsonParseException( ParsingError.InvalidOrUnexpectedCharacter, errorPosition); } } } return jsonArray; } private JsonValue Parse() { _scanner.SkipWhitespace(); return ReadJsonValue(); } }