438 lines
13 KiB
C#
438 lines
13 KiB
C#
// Copyright (c) All contributors. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.Serialization;
|
|
using System.Text;
|
|
|
|
#pragma warning disable SA1649 // File name should match first type name
|
|
|
|
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,
|
|
}
|
|
|
|
[Serializable]
|
|
public class TinyJsonException : MessagePackSerializationException
|
|
{
|
|
public TinyJsonException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
protected TinyJsonException(SerializationInfo info, StreamingContext context)
|
|
: base(info, context)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class TinyJsonReader : IDisposable
|
|
{
|
|
private readonly TextReader reader;
|
|
private readonly bool disposeInnerReader;
|
|
private 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()
|
|
{
|
|
this.ReadNextToken();
|
|
this.ReadValue();
|
|
return this.TokenType != TinyJsonToken.None;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (this.reader != null && this.disposeInnerReader)
|
|
{
|
|
this.reader.Dispose();
|
|
}
|
|
|
|
this.TokenType = TinyJsonToken.None;
|
|
this.ValueType = ValueType.Null;
|
|
}
|
|
|
|
private void SkipWhiteSpace()
|
|
{
|
|
var c = this.reader.Peek();
|
|
while (c != -1 && Char.IsWhiteSpace((char)c))
|
|
{
|
|
this.reader.Read();
|
|
c = this.reader.Peek();
|
|
}
|
|
}
|
|
|
|
private char ReadChar()
|
|
{
|
|
return (char)this.reader.Read();
|
|
}
|
|
|
|
private static bool IsWordBreak(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case ' ':
|
|
case '{':
|
|
case '}':
|
|
case '[':
|
|
case ']':
|
|
case ',':
|
|
case ':':
|
|
case '\"':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void ReadNextToken()
|
|
{
|
|
this.SkipWhiteSpace();
|
|
|
|
var intChar = this.reader.Peek();
|
|
if (intChar == -1)
|
|
{
|
|
this.TokenType = TinyJsonToken.None;
|
|
return;
|
|
}
|
|
|
|
var c = (char)intChar;
|
|
switch (c)
|
|
{
|
|
case '{':
|
|
this.TokenType = TinyJsonToken.StartObject;
|
|
return;
|
|
case '}':
|
|
this.TokenType = TinyJsonToken.EndObject;
|
|
return;
|
|
case '[':
|
|
this.TokenType = TinyJsonToken.StartArray;
|
|
return;
|
|
case ']':
|
|
this.TokenType = TinyJsonToken.EndArray;
|
|
return;
|
|
case '"':
|
|
this.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 '-':
|
|
this.TokenType = TinyJsonToken.Number;
|
|
return;
|
|
case 't':
|
|
this.TokenType = TinyJsonToken.True;
|
|
return;
|
|
case 'f':
|
|
this.TokenType = TinyJsonToken.False;
|
|
return;
|
|
case 'n':
|
|
this.TokenType = TinyJsonToken.Null;
|
|
return;
|
|
case ',':
|
|
case ':':
|
|
this.reader.Read();
|
|
this.ReadNextToken();
|
|
return;
|
|
default:
|
|
throw new TinyJsonException("Invalid String:" + c);
|
|
}
|
|
}
|
|
|
|
private void ReadValue()
|
|
{
|
|
this.ValueType = ValueType.Null;
|
|
|
|
switch (this.TokenType)
|
|
{
|
|
case TinyJsonToken.None:
|
|
break;
|
|
case TinyJsonToken.StartObject:
|
|
case TinyJsonToken.EndObject:
|
|
case TinyJsonToken.StartArray:
|
|
case TinyJsonToken.EndArray:
|
|
this.reader.Read();
|
|
break;
|
|
case TinyJsonToken.Number:
|
|
this.ReadNumber();
|
|
break;
|
|
case TinyJsonToken.String:
|
|
this.ReadString();
|
|
break;
|
|
case TinyJsonToken.True:
|
|
if (this.ReadChar() != 't')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'r')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'u')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'e')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
this.ValueType = ValueType.True;
|
|
break;
|
|
case TinyJsonToken.False:
|
|
if (this.ReadChar() != 'f')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'a')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'l')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 's')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'e')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
this.ValueType = ValueType.False;
|
|
break;
|
|
case TinyJsonToken.Null:
|
|
if (this.ReadChar() != 'n')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'u')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'l')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
if (this.ReadChar() != 'l')
|
|
{
|
|
throw new TinyJsonException("Invalid Token");
|
|
}
|
|
|
|
this.ValueType = ValueType.Null;
|
|
break;
|
|
default:
|
|
throw new MessagePackSerializationException("InvalidTokenState:" + this.TokenType);
|
|
}
|
|
}
|
|
|
|
private void ReadNumber()
|
|
{
|
|
StringBuilder numberWord;
|
|
if (this.reusableBuilder == null)
|
|
{
|
|
this.reusableBuilder = new StringBuilder();
|
|
numberWord = this.reusableBuilder;
|
|
}
|
|
else
|
|
{
|
|
numberWord = this.reusableBuilder;
|
|
numberWord.Length = 0; // Clear
|
|
}
|
|
|
|
var isDouble = false;
|
|
var intChar = this.reader.Peek();
|
|
while (intChar != -1 && !IsWordBreak((char)intChar))
|
|
{
|
|
var c = this.ReadChar();
|
|
numberWord.Append(c);
|
|
if (c == '.' || c == 'e' || c == 'E')
|
|
{
|
|
isDouble = true;
|
|
}
|
|
|
|
intChar = this.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);
|
|
this.ValueType = ValueType.Double;
|
|
this.DoubleValue = parsedDouble;
|
|
}
|
|
else
|
|
{
|
|
long parsedInt;
|
|
if (Int64.TryParse(number, NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out parsedInt))
|
|
{
|
|
this.ValueType = ValueType.Long;
|
|
this.LongValue = parsedInt;
|
|
return;
|
|
}
|
|
|
|
ulong parsedULong;
|
|
if (ulong.TryParse(number, NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out parsedULong))
|
|
{
|
|
this.ValueType = ValueType.ULong;
|
|
this.ULongValue = parsedULong;
|
|
return;
|
|
}
|
|
|
|
Decimal parsedDecimal;
|
|
if (decimal.TryParse(number, NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out parsedDecimal))
|
|
{
|
|
this.ValueType = ValueType.Decimal;
|
|
this.DecimalValue = parsedDecimal;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadString()
|
|
{
|
|
this.reader.Read(); // skip ["]
|
|
|
|
StringBuilder sb;
|
|
if (this.reusableBuilder == null)
|
|
{
|
|
this.reusableBuilder = new StringBuilder();
|
|
sb = this.reusableBuilder;
|
|
}
|
|
else
|
|
{
|
|
sb = this.reusableBuilder;
|
|
sb.Length = 0; // Clear
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (this.reader.Peek() == -1)
|
|
{
|
|
throw new TinyJsonException("Invalid Json String");
|
|
}
|
|
|
|
var c = this.ReadChar();
|
|
switch (c)
|
|
{
|
|
case '"': // endtoken
|
|
goto END;
|
|
case '\\': // escape character
|
|
if (this.reader.Peek() == -1)
|
|
{
|
|
throw new TinyJsonException("Invalid Json String");
|
|
}
|
|
|
|
c = this.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] = this.ReadChar();
|
|
hex[1] = this.ReadChar();
|
|
hex[2] = this.ReadChar();
|
|
hex[3] = this.ReadChar();
|
|
sb.Append((char)Convert.ToInt32(new string(hex), 16));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default: // string
|
|
sb.Append(c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
END:
|
|
this.ValueType = ValueType.String;
|
|
this.StringValue = sb.ToString();
|
|
}
|
|
}
|
|
}
|