|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace Xunit
{
class JsonArray : JsonValue
{
readonly JsonValue[] _array;
public JsonArray(JsonValue[] array, int line, int column)
: base(line, column)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
_array = array;
}
public int Length
{
get { return _array.Length; }
}
public JsonValue this[int index]
{
get { return _array[index]; }
}
}
class JsonBoolean : JsonValue
{
public JsonBoolean(JsonToken token)
: base(token.Line, token.Column)
{
if (token.Type == JsonTokenType.True)
{
Value = true;
}
else if (token.Type == JsonTokenType.False)
{
Value = false;
}
else
{
throw new ArgumentException("Token value should be either True or False.", nameof(token));
}
}
public bool Value { get; private set; }
public static implicit operator bool (JsonBoolean jsonBoolean)
{
return jsonBoolean.Value;
}
}
class JsonBuffer
{
public const string ValueNull = "null";
public const string ValueTrue = "true";
public const string ValueFalse = "false";
StringBuilder _buffer = new StringBuilder();
StringBuilder _codePointBuffer = new StringBuilder(4);
readonly TextReader _reader;
JsonToken _token;
int _line;
int _column;
public JsonBuffer(TextReader reader)
{
_reader = reader;
_line = 1;
}
public JsonToken Read()
{
int first;
while (true)
{
first = ReadNextChar();
if (first == -1)
{
_token.Type = JsonTokenType.EOF;
return _token;
}
else if (!IsWhitespace(first))
{
break;
}
}
_token.Value = ((char)first).ToString();
_token.Line = _line;
_token.Column = _column;
if (first == '{')
{
_token.Type = JsonTokenType.LeftCurlyBracket;
}
else if (first == '}')
{
_token.Type = JsonTokenType.RightCurlyBracket;
}
else if (first == '[')
{
_token.Type = JsonTokenType.LeftSquareBracket;
}
else if (first == ']')
{
_token.Type = JsonTokenType.RightSquareBracket;
}
else if (first == ':')
{
_token.Type = JsonTokenType.Colon;
}
else if (first == ',')
{
_token.Type = JsonTokenType.Comma;
}
else if (first == '"')
{
_token.Type = JsonTokenType.String;
_token.Value = ReadString();
}
else if (first == 't')
{
ReadLiteral(ValueTrue);
_token.Type = JsonTokenType.True;
}
else if (first == 'f')
{
ReadLiteral(ValueFalse);
_token.Type = JsonTokenType.False;
}
else if (first == 'n')
{
ReadLiteral(ValueNull);
_token.Type = JsonTokenType.Null;
}
else if ((first >= '0' && first <= '9') || first == '-')
{
_token.Type = JsonTokenType.Number;
_token.Value = ReadNumber(first);
}
else
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_IllegalCharacter(first),
_token);
}
// JsonToken is a value type
return _token;
}
int ReadNextChar()
{
while (true)
{
var value = _reader.Read();
_column++;
switch (value)
{
case -1:
// This is the end of file
return -1;
case '\n':
// This is a new line. Let the next loop read the first character of the following line.
// Set position ahead of next line
_column = 0;
_line++;
continue;
case '\r':
break;
default:
// Returns the normal value
return value;
}
}
}
string ReadNumber(int firstRead)
{
#if NET35
_buffer = new StringBuilder();
#else
_buffer.Clear();
#endif
_buffer.Append((char)firstRead);
while (true)
{
var next = _reader.Peek();
if ((next >= '0' && next <= '9') ||
next == '.' ||
next == 'e' ||
next == 'E')
{
_buffer.Append((char)ReadNextChar());
}
else
{
break;
}
}
return _buffer.ToString();
}
void ReadLiteral(string literal)
{
for (int i = 1; i < literal.Length; ++i)
{
var next = _reader.Peek();
if (next != literal[i])
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_UnrecognizedLiteral(literal),
_line, _column);
}
else
{
ReadNextChar();
}
}
var tail = _reader.Peek();
if (tail != '}' &&
tail != ']' &&
tail != ',' &&
tail != '\n' &&
tail != -1 &&
!IsWhitespace(tail))
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_IllegalTrailingCharacterAfterLiteral(tail, literal),
_line, _column);
}
}
string ReadString()
{
#if NET35
_buffer = new StringBuilder();
#else
_buffer.Clear();
#endif
var escaped = false;
while (true)
{
var next = ReadNextChar();
if (next == -1 || next == '\n')
{
throw new JsonDeserializerException(
JsonDeserializerResource.JSON_OpenString,
_line, _column);
}
else if (escaped)
{
if ((next == '"') || (next == '\\') || (next == '/'))
{
_buffer.Append((char)next);
}
else if (next == 'b')
{
// '\b' backspace
_buffer.Append('\b');
}
else if (next == 'f')
{
// '\f' form feed
_buffer.Append('\f');
}
else if (next == 'n')
{
// '\n' line feed
_buffer.Append('\n');
}
else if (next == 'r')
{
// '\r' carriage return
_buffer.Append('\r');
}
else if (next == 't')
{
// '\t' tab
_buffer.Append('\t');
}
else if (next == 'u')
{
// '\uXXXX' unicode
var unicodeLine = _line;
var unicodeColumn = _column;
#if NET35
_codePointBuffer = new StringBuilder(4);
#else
_codePointBuffer.Clear();
#endif
for (int i = 0; i < 4; ++i)
{
next = ReadNextChar();
if (next == -1)
{
throw new JsonDeserializerException(
JsonDeserializerResource.JSON_InvalidEnd,
unicodeLine,
unicodeColumn);
}
else
{
_codePointBuffer[i] = (char)next;
}
}
try
{
var unicodeValue = int.Parse(_codePointBuffer.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
_buffer.Append((char)unicodeValue);
}
catch (FormatException ex)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidUnicode(_codePointBuffer.ToString()),
ex,
unicodeLine,
unicodeColumn);
}
}
else
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxNotExpected("character escape", "\\" + next),
_line,
_column);
}
escaped = false;
}
else if (next == '\\')
{
escaped = true;
}
else if (next == '"')
{
break;
}
else
{
_buffer.Append((char)next);
}
}
return _buffer.ToString();
}
static bool IsWhitespace(int value)
{
return value == ' ' || value == '\t' || value == '\r';
}
}
static class JsonDeserializer
{
public static JsonValue Deserialize(TextReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
var buffer = new JsonBuffer(reader);
var result = DeserializeInternal(buffer.Read(), buffer);
// There are still unprocessed char. The parsing is not finished. Error happened.
var nextToken = buffer.Read();
if (nextToken.Type != JsonTokenType.EOF)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_UnfinishedJSON(nextToken.Value),
nextToken);
}
return result;
}
static JsonValue DeserializeInternal(JsonToken next, JsonBuffer buffer)
{
if (next.Type == JsonTokenType.EOF)
{
return null;
}
if (next.Type == JsonTokenType.LeftSquareBracket)
{
return DeserializeArray(next, buffer);
}
if (next.Type == JsonTokenType.LeftCurlyBracket)
{
return DeserializeObject(next, buffer);
}
if (next.Type == JsonTokenType.String)
{
return new JsonString(next.Value, next.Line, next.Column);
}
if (next.Type == JsonTokenType.True || next.Type == JsonTokenType.False)
{
return new JsonBoolean(next);
}
if (next.Type == JsonTokenType.Null)
{
return new JsonNull(next.Line, next.Column);
}
if (next.Type == JsonTokenType.Number)
{
return new JsonNumber(next);
}
throw new JsonDeserializerException(JsonDeserializerResource.Format_InvalidTokenExpectation(
next.Value, "'{', '[', true, false, null, JSON string, JSON number, or the end of the file"),
next);
}
static JsonArray DeserializeArray(JsonToken head, JsonBuffer buffer)
{
var list = new List<JsonValue>();
while (true)
{
var next = buffer.Read();
if (next.Type == JsonTokenType.RightSquareBracket)
{
break;
}
list.Add(DeserializeInternal(next, buffer));
next = buffer.Read();
if (next.Type == JsonTokenType.EOF)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ']', ','),
next);
}
else if (next.Type == JsonTokenType.RightSquareBracket)
{
break;
}
else if (next.Type != JsonTokenType.Comma)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ','),
next);
}
}
return new JsonArray(list.ToArray(), head.Line, head.Column);
}
static JsonObject DeserializeObject(JsonToken head, JsonBuffer buffer)
{
var dictionary = new Dictionary<string, JsonValue>();
// Loop through each JSON entry in the input object
while (true)
{
var next = buffer.Read();
if (next.Type == JsonTokenType.EOF)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", '}'),
next);
}
if (next.Type == JsonTokenType.Colon)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxNotExpected("JSON object", ':'),
next);
}
else if (next.Type == JsonTokenType.RightCurlyBracket)
{
break;
}
else
{
if (next.Type != JsonTokenType.String)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object member name", "JSON string"),
next);
}
var memberName = next.Value;
if (dictionary.ContainsKey(memberName))
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_DuplicateObjectMemberName(memberName),
next);
}
next = buffer.Read();
if (next.Type != JsonTokenType.Colon)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ':'),
next);
}
dictionary[memberName] = DeserializeInternal(buffer.Read(), buffer);
next = buffer.Read();
if (next.Type == JsonTokenType.RightCurlyBracket)
{
break;
}
else if (next.Type != JsonTokenType.Comma)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ',', '}'),
next);
}
}
}
return new JsonObject(dictionary, head.Line, head.Column);
}
}
class JsonDeserializerException : Exception
{
public JsonDeserializerException(string message, Exception innerException, int line, int column)
: base(message, innerException)
{
Line = line;
Column = column;
}
public JsonDeserializerException(string message, int line, int column)
: base(message)
{
Line = line;
Column = column;
}
public JsonDeserializerException(string message, JsonToken nextToken)
: base(message)
{
Line = nextToken.Line;
Column = nextToken.Column;
}
public int Line { get; }
public int Column { get; }
}
static class JsonDeserializerResource
{
internal static string Format_IllegalCharacter(int value)
{
return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4}).";
}
internal static string Format_IllegalTrailingCharacterAfterLiteral(int value, string literal)
{
return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4}) after the literal name '{literal}'.";
}
internal static string Format_UnrecognizedLiteral(string literal)
{
return $"Invalid JSON literal. Expected literal '{literal}'.";
}
internal static string Format_DuplicateObjectMemberName(string memberName)
{
return Format_InvalidSyntax("JSON object", $"Duplicate member name '{memberName}'");
}
internal static string Format_InvalidFloatNumberFormat(string raw)
{
return $"Invalid float number format: {raw}";
}
internal static string Format_FloatNumberOverflow(string raw)
{
return $"Float number overflow: {raw}";
}
internal static string Format_InvalidSyntax(string syntaxName, string issue)
{
return $"Invalid {syntaxName} syntax. {issue}.";
}
internal static string Format_InvalidSyntaxNotExpected(string syntaxName, char unexpected)
{
return $"Invalid {syntaxName} syntax. Unexpected '{unexpected}'.";
}
internal static string Format_InvalidSyntaxNotExpected(string syntaxName, string unexpected)
{
return $"Invalid {syntaxName} syntax. Unexpected {unexpected}.";
}
internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation)
{
return $"Invalid {syntaxName} syntax. Expected '{expectation}'.";
}
internal static string Format_InvalidSyntaxExpectation(string syntaxName, string expectation)
{
return $"Invalid {syntaxName} syntax. Expected {expectation}.";
}
internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation1, char expectation2)
{
return $"Invalid {syntaxName} syntax. Expected '{expectation1}' or '{expectation2}'.";
}
internal static string Format_InvalidTokenExpectation(string tokenValue, string expectation)
{
return $"Unexpected token '{tokenValue}'. Expected {expectation}.";
}
internal static string Format_InvalidUnicode(string unicode)
{
return $"Invalid Unicode [{unicode}]";
}
internal static string Format_UnfinishedJSON(string nextTokenValue)
{
return $"Invalid JSON end. Unprocessed token {nextTokenValue}.";
}
internal static string JSON_OpenString
{
get { return Format_InvalidSyntaxExpectation("JSON string", '\"'); }
}
internal static string JSON_InvalidEnd
{
get { return "Invalid JSON. Unexpected end of file."; }
}
}
class JsonNull : JsonValue
{
public JsonNull(int line, int column)
: base(line, column)
{
}
}
class JsonNumber : JsonValue
{
readonly string _raw;
readonly double _double;
public JsonNumber(JsonToken token)
: base(token.Line, token.Column)
{
try
{
_raw = token.Value;
_double = double.Parse(_raw, NumberStyles.Float);
}
catch (FormatException ex)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_InvalidFloatNumberFormat(_raw),
ex,
token.Line,
token.Column);
}
catch (OverflowException ex)
{
throw new JsonDeserializerException(
JsonDeserializerResource.Format_FloatNumberOverflow(_raw),
ex,
token.Line,
token.Column);
}
}
public double Double
{
get { return _double; }
}
public string Raw
{
get { return _raw; }
}
}
class JsonObject : JsonValue
{
readonly IDictionary<string, JsonValue> _data;
public JsonObject(IDictionary<string, JsonValue> data, int line, int column)
: base(line, column)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
_data = data;
}
public ICollection<string> Keys
{
get { return _data.Keys; }
}
public JsonValue Value(string key)
{
JsonValue result;
if (!_data.TryGetValue(key, out result))
{
result = null;
}
return result;
}
public JsonObject ValueAsJsonObject(string key)
{
return Value(key) as JsonObject;
}
public JsonString ValueAsString(string key)
{
return Value(key) as JsonString;
}
public int ValueAsInt(string key)
{
var number = Value(key) as JsonNumber;
if (number == null)
{
throw new FormatException();
}
return Convert.ToInt32(number.Raw);
}
public bool ValueAsBoolean(string key, bool defaultValue = false)
{
var boolVal = Value(key) as JsonBoolean;
if (boolVal != null)
{
return boolVal.Value;
}
return defaultValue;
}
public bool? ValueAsNullableBoolean(string key)
{
var boolVal = Value(key) as JsonBoolean;
if (boolVal != null)
{
return boolVal.Value;
}
return null;
}
public string[] ValueAsStringArray(string key)
{
var list = Value(key) as JsonArray;
if (list == null)
{
return null;
}
var result = new string[list.Length];
for (int i = 0; i < list.Length; ++i)
{
var jsonString = list[i] as JsonString;
result[i] = jsonString?.ToString();
}
return result;
}
}
class JsonString : JsonValue
{
readonly string _value;
public JsonString(string value, int line, int column)
: base(line, column)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_value = value;
}
public string Value
{
get { return _value; }
}
public override string ToString()
{
return _value;
}
public static implicit operator string (JsonString instance)
{
if (instance == null)
{
return null;
}
else
{
return instance.Value;
}
}
}
struct JsonToken
{
public JsonTokenType Type;
public string Value;
public int Line;
public int Column;
}
enum JsonTokenType
{
LeftCurlyBracket, // [
LeftSquareBracket, // {
RightCurlyBracket, // ]
RightSquareBracket, // }
Colon, // :
Comma, // ,
Null,
True,
False,
Number,
String,
EOF
}
class JsonValue
{
public JsonValue(int line, int column)
{
Line = line;
Column = column;
}
public int Line { get; }
public int Column { get; }
}
}
|