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(); } } }