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.
355 lines
12 KiB
355 lines
12 KiB
using System; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Text; |
|
|
|
namespace MessagePack |
|
{ |
|
// simple, tiny JSON reader for MessagePackSerializer.FromJson. |
|
// this is simple, compact and enough fast but not optimized extremely. |
|
|
|
internal enum TinyJsonToken |
|
{ |
|
None, |
|
StartObject, // { |
|
EndObject, // } |
|
StartArray, // [ |
|
EndArray, // ] |
|
Number, // -0~9 |
|
String, // "___" |
|
True, // true |
|
False, // false |
|
Null, // null |
|
} |
|
|
|
internal enum ValueType : byte |
|
{ |
|
Null, |
|
True, |
|
False, |
|
Double, |
|
Long, |
|
ULong, |
|
Decimal, |
|
String |
|
} |
|
|
|
internal class TinyJsonException : Exception |
|
{ |
|
public TinyJsonException(string message) : base(message) |
|
{ |
|
|
|
} |
|
} |
|
|
|
internal class TinyJsonReader : IDisposable |
|
{ |
|
readonly TextReader reader; |
|
readonly bool disposeInnerReader; |
|
StringBuilder reusableBuilder; |
|
|
|
public TinyJsonToken TokenType { get; private set; } |
|
public ValueType ValueType { get; private set; } |
|
public double DoubleValue { get; private set; } |
|
public long LongValue { get; private set; } |
|
public ulong ULongValue { get; private set; } |
|
public decimal DecimalValue { get; private set; } |
|
public string StringValue { get; private set; } |
|
|
|
public TinyJsonReader(TextReader reader, bool disposeInnerReader = true) |
|
{ |
|
this.reader = reader; |
|
this.disposeInnerReader = disposeInnerReader; |
|
} |
|
|
|
public bool Read() |
|
{ |
|
ReadNextToken(); |
|
ReadValue(); |
|
return TokenType != TinyJsonToken.None; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
if (reader != null && disposeInnerReader) |
|
{ |
|
reader.Dispose(); |
|
} |
|
TokenType = TinyJsonToken.None; |
|
ValueType = ValueType.Null; |
|
} |
|
|
|
void SkipWhiteSpace() |
|
{ |
|
var c = reader.Peek(); |
|
while (c != -1 && Char.IsWhiteSpace((char)c)) |
|
{ |
|
reader.Read(); |
|
c = reader.Peek(); |
|
} |
|
} |
|
|
|
char ReadChar() |
|
{ |
|
return (char)reader.Read(); |
|
} |
|
|
|
static bool IsWordBreak(char c) |
|
{ |
|
switch (c) |
|
{ |
|
case ' ': |
|
case '{': |
|
case '}': |
|
case '[': |
|
case ']': |
|
case ',': |
|
case ':': |
|
case '\"': |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
void ReadNextToken() |
|
{ |
|
SkipWhiteSpace(); |
|
|
|
var intChar = reader.Peek(); |
|
if (intChar == -1) |
|
{ |
|
TokenType = TinyJsonToken.None; |
|
return; |
|
} |
|
|
|
var c = (char)intChar; |
|
switch (c) |
|
{ |
|
case '{': |
|
TokenType = TinyJsonToken.StartObject; |
|
return; |
|
case '}': |
|
TokenType = TinyJsonToken.EndObject; |
|
return; |
|
case '[': |
|
TokenType = TinyJsonToken.StartArray; |
|
return; |
|
case ']': |
|
TokenType = TinyJsonToken.EndArray; |
|
return; |
|
case '"': |
|
TokenType = TinyJsonToken.String; |
|
return; |
|
case '0': |
|
case '1': |
|
case '2': |
|
case '3': |
|
case '4': |
|
case '5': |
|
case '6': |
|
case '7': |
|
case '8': |
|
case '9': |
|
case '-': |
|
TokenType = TinyJsonToken.Number; |
|
return; |
|
case 't': |
|
TokenType = TinyJsonToken.True; |
|
return; |
|
case 'f': |
|
TokenType = TinyJsonToken.False; |
|
return; |
|
case 'n': |
|
TokenType = TinyJsonToken.Null; |
|
return; |
|
case ',': |
|
case ':': |
|
reader.Read(); |
|
ReadNextToken(); |
|
return; |
|
default: |
|
throw new TinyJsonException("Invalid String:" + c); |
|
} |
|
} |
|
|
|
void ReadValue() |
|
{ |
|
ValueType = ValueType.Null; |
|
|
|
switch (TokenType) |
|
{ |
|
case TinyJsonToken.None: |
|
break; |
|
case TinyJsonToken.StartObject: |
|
case TinyJsonToken.EndObject: |
|
case TinyJsonToken.StartArray: |
|
case TinyJsonToken.EndArray: |
|
reader.Read(); |
|
break; |
|
case TinyJsonToken.Number: |
|
ReadNumber(); |
|
break; |
|
case TinyJsonToken.String: |
|
ReadString(); |
|
break; |
|
case TinyJsonToken.True: |
|
if (ReadChar() != 't') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'r') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'u') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'e') throw new TinyJsonException("Invalid Token"); |
|
ValueType = ValueType.True; |
|
break; |
|
case TinyJsonToken.False: |
|
if (ReadChar() != 'f') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'a') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'l') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 's') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'e') throw new TinyJsonException("Invalid Token"); |
|
ValueType = ValueType.False; |
|
break; |
|
case TinyJsonToken.Null: |
|
if (ReadChar() != 'n') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'u') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'l') throw new TinyJsonException("Invalid Token"); |
|
if (ReadChar() != 'l') throw new TinyJsonException("Invalid Token"); |
|
ValueType = ValueType.Null; |
|
break; |
|
default: |
|
throw new ArgumentException("InvalidTokenState:" + TokenType); |
|
} |
|
} |
|
|
|
void ReadNumber() |
|
{ |
|
StringBuilder numberWord; |
|
if (reusableBuilder == null) |
|
{ |
|
reusableBuilder = new StringBuilder(); |
|
numberWord = reusableBuilder; |
|
} |
|
else |
|
{ |
|
numberWord = reusableBuilder; |
|
numberWord.Length = 0; // Clear |
|
} |
|
|
|
var isDouble = false; |
|
var intChar = reader.Peek(); |
|
while (intChar != -1 && !IsWordBreak((char)intChar)) |
|
{ |
|
var c = ReadChar(); |
|
numberWord.Append(c); |
|
if (c == '.' || c == 'e' || c == 'E') isDouble = true; |
|
intChar = reader.Peek(); |
|
} |
|
|
|
var number = numberWord.ToString(); |
|
if (isDouble) |
|
{ |
|
double parsedDouble; |
|
Double.TryParse(number, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent, System.Globalization.CultureInfo.InvariantCulture, out parsedDouble); |
|
ValueType = ValueType.Double; |
|
DoubleValue = parsedDouble; |
|
} |
|
else |
|
{ |
|
long parsedInt; |
|
if (Int64.TryParse(number, NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out parsedInt)) |
|
{ |
|
ValueType = ValueType.Long; |
|
LongValue = parsedInt; |
|
return; |
|
} |
|
|
|
ulong parsedULong; |
|
if (ulong.TryParse(number, NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out parsedULong)) |
|
{ |
|
ValueType = ValueType.ULong; |
|
ULongValue = parsedULong; |
|
return; |
|
} |
|
|
|
Decimal parsedDecimal; |
|
if (decimal.TryParse(number, NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out parsedDecimal)) |
|
{ |
|
ValueType = ValueType.Decimal; |
|
DecimalValue = parsedDecimal; |
|
return; |
|
} |
|
} |
|
} |
|
|
|
void ReadString() |
|
{ |
|
reader.Read(); // skip ["] |
|
|
|
StringBuilder sb; |
|
if (reusableBuilder == null) |
|
{ |
|
reusableBuilder = new StringBuilder(); |
|
sb = reusableBuilder; |
|
} |
|
else |
|
{ |
|
sb = reusableBuilder; |
|
sb.Length = 0; // Clear |
|
} |
|
|
|
while (true) |
|
{ |
|
if (reader.Peek() == -1) throw new TinyJsonException("Invalid Json String"); |
|
|
|
var c = ReadChar(); |
|
switch (c) |
|
{ |
|
case '"': // endtoken |
|
goto END; |
|
case '\\': // escape character |
|
if (reader.Peek() == -1) throw new TinyJsonException("Invalid Json String"); |
|
|
|
c = ReadChar(); |
|
switch (c) |
|
{ |
|
case '"': |
|
case '\\': |
|
case '/': |
|
sb.Append(c); |
|
break; |
|
case 'b': |
|
sb.Append('\b'); |
|
break; |
|
case 'f': |
|
sb.Append('\f'); |
|
break; |
|
case 'n': |
|
sb.Append('\n'); |
|
break; |
|
case 'r': |
|
sb.Append('\r'); |
|
break; |
|
case 't': |
|
sb.Append('\t'); |
|
break; |
|
case 'u': |
|
var hex = new char[4]; |
|
hex[0] = ReadChar(); |
|
hex[1] = ReadChar(); |
|
hex[2] = ReadChar(); |
|
hex[3] = ReadChar(); |
|
sb.Append((char)Convert.ToInt32(new string(hex), 16)); |
|
break; |
|
} |
|
break; |
|
default: // string |
|
sb.Append(c); |
|
break; |
|
} |
|
} |
|
|
|
END: |
|
ValueType = ValueType.String; |
|
StringValue = sb.ToString(); |
|
} |
|
} |
|
} |