.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

413 lines
8.0 KiB

// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
namespace LightJson.Serialization
{
using System;
using System.Globalization;
using System.IO;
using System.Text;
using ErrorType = JsonParseException.ErrorType;
/// <summary>
/// Represents a reader that can read JsonValues.
/// </summary>
internal sealed class JsonReader
{
private TextScanner scanner;
private JsonReader(TextReader reader)
{
this.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 this.ReadString();
}
private JsonValue ReadJsonValue()
{
this.scanner.SkipWhitespace();
var next = this.scanner.Peek();
if (char.IsNumber(next)) {
return this.ReadNumber();
}
switch (next) {
case '{':
return this.ReadObject();
case '[':
return this.ReadArray();
case '"':
return this.ReadString();
case '-':
return this.ReadNumber();
case 't':
case 'f':
return this.ReadBoolean();
case 'n':
return this.ReadNull();
default:
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
this.scanner.Position);
}
}
private JsonValue ReadNull()
{
this.scanner.Assert("null");
return JsonValue.Null;
}
private JsonValue ReadBoolean()
{
switch (this.scanner.Peek()) {
case 't':
this.scanner.Assert("true");
return true;
default:
this.scanner.Assert("false");
return false;
}
}
private void ReadDigits(StringBuilder builder)
{
while (true) {
int next = this.scanner.Peek(throwAtEndOfFile: false);
if (next == -1 || !char.IsNumber((char)next)) {
return;
}
builder.Append(this.scanner.Read());
}
}
private JsonValue ReadNumber()
{
var builder = new StringBuilder();
if (this.scanner.Peek() == '-') {
builder.Append(this.scanner.Read());
}
if (this.scanner.Peek() == '0') {
builder.Append(this.scanner.Read());
} else {
this.ReadDigits(builder);
}
if (this.scanner.Peek(throwAtEndOfFile: false) == '.') {
builder.Append(this.scanner.Read());
this.ReadDigits(builder);
}
if (this.scanner.Peek(throwAtEndOfFile: false) == 'e' || this.scanner.Peek(throwAtEndOfFile: false) == 'E') {
builder.Append(this.scanner.Read());
var next = this.scanner.Peek();
switch (next) {
case '+':
case '-':
builder.Append(this.scanner.Read());
break;
}
this.ReadDigits(builder);
}
return double.Parse(
builder.ToString(),
CultureInfo.InvariantCulture);
}
private string ReadString()
{
var builder = new StringBuilder();
this.scanner.Assert('"');
while (true) {
var errorPosition = this.scanner.Position;
var c = this.scanner.Read();
if (c == '\\') {
errorPosition = this.scanner.Position;
c = this.scanner.Read();
switch (char.ToLower(c)) {
case '"':
case '\\':
case '/':
builder.Append(c);
break;
case 'b':
builder.Append('\b');
break;
case 'f':
builder.Append('\f');
break;
case 'n':
builder.Append('\n');
break;
case 'r':
builder.Append('\r');
break;
case 't':
builder.Append('\t');
break;
case 'u':
builder.Append(this.ReadUnicodeLiteral());
break;
default:
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
errorPosition);
}
} else if (c == '"') {
break;
} else {
if (char.IsControl(c)) {
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
errorPosition);
} else {
builder.Append(c);
}
}
}
return builder.ToString();
}
private int ReadHexDigit()
{
var errorPosition = this.scanner.Position;
switch (char.ToUpper(this.scanner.Read())) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'A':
return 10;
case 'B':
return 11;
case 'C':
return 12;
case 'D':
return 13;
case 'E':
return 14;
case 'F':
return 15;
default:
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
errorPosition);
}
}
private char ReadUnicodeLiteral()
{
int value = 0;
value += this.ReadHexDigit() * 4096; // 16^3
value += this.ReadHexDigit() * 256; // 16^2
value += this.ReadHexDigit() * 16; // 16^1
value += this.ReadHexDigit(); // 16^0
return (char)value;
}
private JsonObject ReadObject()
{
return this.ReadObject(new JsonObject());
}
private JsonObject ReadObject(JsonObject jsonObject)
{
this.scanner.Assert('{');
this.scanner.SkipWhitespace();
if (this.scanner.Peek() == '}') {
this.scanner.Read();
} else {
while (true) {
this.scanner.SkipWhitespace();
var errorPosition = this.scanner.Position;
var key = this.ReadJsonKey();
if (jsonObject.ContainsKey(key)) {
throw new JsonParseException(
ErrorType.DuplicateObjectKeys,
errorPosition);
}
this.scanner.SkipWhitespace();
this.scanner.Assert(':');
this.scanner.SkipWhitespace();
var value = this.ReadJsonValue();
jsonObject.Add(key, value);
this.scanner.SkipWhitespace();
errorPosition = this.scanner.Position;
var next = this.scanner.Read();
if (next == ',') {
// Allow trailing commas in objects
this.scanner.SkipWhitespace();
if (this.scanner.Peek() == '}') {
next = this.scanner.Read();
}
}
if (next == '}') {
break;
} else if (next == ',') {
continue;
} else {
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
errorPosition);
}
}
}
return jsonObject;
}
private JsonArray ReadArray()
{
return this.ReadArray(new JsonArray());
}
private JsonArray ReadArray(JsonArray jsonArray)
{
this.scanner.Assert('[');
this.scanner.SkipWhitespace();
if (this.scanner.Peek() == ']') {
this.scanner.Read();
} else {
while (true) {
this.scanner.SkipWhitespace();
var value = this.ReadJsonValue();
jsonArray.Add(value);
this.scanner.SkipWhitespace();
var errorPosition = this.scanner.Position;
var next = this.scanner.Read();
if (next == ',') {
// Allow trailing commas in arrays
this.scanner.SkipWhitespace();
if (this.scanner.Peek() == ']') {
next = this.scanner.Read();
}
}
if (next == ']') {
break;
} else if (next == ',') {
continue;
} else {
throw new JsonParseException(
ErrorType.InvalidOrUnexpectedCharacter,
errorPosition);
}
}
}
return jsonArray;
}
private JsonValue Parse()
{
this.scanner.SkipWhitespace();
return this.ReadJsonValue();
}
}
}