using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; namespace JSON { public enum Type { Null, Boolean, Number, String, Array, Object } public struct Value : IEquatable, IEnumerable> { private Type mType; private Object mValue; private Int32 mArrayLength; internal Value(Type kind) { mType = kind; if (mType == Type.Array) mValue = new Dictionary(); else if (mType == Type.Object) mValue = new Dictionary(); else mValue = null; mArrayLength = 0; } internal Value(Boolean value) { mType = Type.Boolean; mValue = value; mArrayLength = 0; } internal Value(Double value) { mType = Type.Number; mValue = value; mArrayLength = 0; } internal Value(String value) { mType = (value == null) ? Type.Null : Type.String; mValue = value; mArrayLength = 0; } public Value this[Int32 index] { get { if (mType != Type.Array) throw new InvalidOperationException("Int32 indexer may only be used with array values"); Dictionary dict = mValue as Dictionary; if (!dict.ContainsKey(index)) return null; return dict[index]; } set { if (mType != Type.Array) throw new InvalidOperationException("Int32 indexer may only be used with array values"); Dictionary dict = mValue as Dictionary; dict[index] = value; if (index >= mArrayLength) mArrayLength = index + 1; } } public Value this[String index] { get { if (mType != Type.Object) throw new InvalidOperationException("String indexer may only be used with array values"); Dictionary dict = mValue as Dictionary; if (!dict.ContainsKey(index)) return null; return dict[index]; } set { if (mType != Type.Object) throw new InvalidOperationException("String indexer may only be used with array values"); Dictionary dict = mValue as Dictionary; dict[index] = value; } } public Object AsObject { get { return mValue; } } public Boolean AsBoolean { get { if (mType != Type.Boolean) throw new InvalidOperationException("Value is not a Boolean"); return (Boolean)mValue; } } public Double AsDouble { get { if (mType != Type.Number) throw new InvalidOperationException("Value is not a Number"); return (Double)mValue; } } public String AsString { get { if (mType == Type.Null) return null; if (mType != Type.String) throw new InvalidOperationException("Value is not a String"); return mValue as String; } } public Type Type { get { return mType; } } public Int32 Count { get { if (mType == Type.Array) { return mArrayLength; } if (mType == Type.Object) { Dictionary dict = mValue as Dictionary; return dict.Count; } return -1; } } public override String ToString() { switch (mType) { case Type.Null: return null; case Type.Boolean: return AsBoolean.ToString(); case Type.Number: return AsDouble.ToString(); case Type.String: return AsString; case Type.Array: return "(JSON.Array)"; case Type.Object: return "(JSON.Object)"; default: throw new InvalidOperationException(); } } public Boolean ContainsKey(String index) { if (mType != Type.Object) throw new InvalidOperationException("Method may only be used with Object values"); Dictionary dict = mValue as Dictionary; return dict.ContainsKey(index); } public static implicit operator Value(Boolean value) { return new Value(value); } public static implicit operator Value(Double value) { return new Value(value); } public static implicit operator Value(String value) { return new Value(value); } public static Boolean operator ==(Value value1, Value value2) { return ((IEquatable)value1).Equals(value2); } public static Boolean operator !=(Value value1, Value value2) { return !((IEquatable)value1).Equals(value2); } Boolean IEquatable.Equals(Value other) { switch (this.mType) { case Type.Null: return (other.mType == Type.Null); case Type.Boolean: return (other.mType == Type.Boolean) && (this.AsBoolean == other.AsBoolean); case Type.Number: return (other.mType == Type.Number) && (this.AsDouble == other.AsDouble); case Type.String: return (other.mType == Type.String) && (this.AsString == other.AsString); case Type.Array: if ((other.mType != Type.Array) || (this.mArrayLength != other.mArrayLength)) return false; Dictionary thisArray = this.mValue as Dictionary; Dictionary otherArray = other.mValue as Dictionary; for (Int32 i = 0; i < this.mArrayLength; i++) { if (thisArray.ContainsKey(i)) { if (!otherArray.ContainsKey(i)) return false; if (thisArray[i] != otherArray[i]) return false; } else { if (otherArray.ContainsKey(i)) return false; } } return true; case Type.Object: if (other.mType != Type.Object) return false; Dictionary thisObject = this.mValue as Dictionary; Dictionary otherObject = other.mValue as Dictionary; foreach (KeyValuePair thisEntry in thisObject) { if (!otherObject.ContainsKey(thisEntry.Key)) return false; if (thisEntry.Value != otherObject[thisEntry.Key]) return false; } foreach (KeyValuePair otherEntry in otherObject) { if (!thisObject.ContainsKey(otherEntry.Key)) return false; } return true; default: throw new InvalidOperationException("Unknown value types cannot be equated"); } } IEnumerator> IEnumerable>.GetEnumerator() { if (mType == Type.Object) { Dictionary dict = mValue as Dictionary; return dict.GetEnumerator(); } throw new InvalidOperationException("Only Object values may be enumerated"); } IEnumerator IEnumerable.GetEnumerator() { if (mType == Type.Object) { Dictionary dict = mValue as Dictionary; return dict.GetEnumerator(); } throw new InvalidOperationException("Only Object values may be enumerated"); } public static Value ReadFrom(TextReader input) { Tokenizer L = new Tokenizer(input); return ReadValue(L); } private static Value ReadValue(Tokenizer input) { Token t = input.ReadToken(); if (t.Type == TokenType.String) { return new Value(t.Value); } else if (t.Type == TokenType.Number) { return new Value(Double.Parse(t.Value)); } else if (t.Type == TokenType.OpenBrace) { Value retval = new Value(Type.Object); t = input.ReadToken(); if (t.Type == TokenType.CloseBrace) return retval; while (t.Type == TokenType.String) { String name = t.Value; t = input.ReadToken(); if (t.Type != TokenType.Colon) throw new InvalidDataException(String.Concat("Parse error at line ", input.LineNum.ToString(), ", character ", input.CharNum.ToString(), ": Colon (:) expected")); retval[name] = ReadValue(input); t = input.ReadToken(); if (t.Type == TokenType.CloseBrace) return retval; if (t.Type != TokenType.Comma) throw new InvalidDataException(String.Concat("Parse error at line ", input.LineNum.ToString(), ", character ", input.CharNum.ToString(), ": Comma (,) or Close Brace (}) expected")); t = input.ReadToken(); } throw new InvalidDataException(String.Concat("Parse error at line ", input.LineNum.ToString(), ", character ", input.CharNum.ToString(), ": String expected")); } else if (t.Type == TokenType.OpenBracket) { Value retval = new Value(Type.Array); input.SkipWhiteSpace(); Nullable c = input.PeekChar(); if ((c.HasValue) && (c.Value == ']')) { input.ReadToken(); return retval; } for (Int32 i = 0; ; i++) { retval[i] = ReadValue(input); t = input.ReadToken(); if (t.Type == TokenType.CloseBracket) return retval; if (t.Type != TokenType.Comma) throw new InvalidDataException(String.Concat("Parse error at line ", input.LineNum.ToString(), ", character ", input.CharNum.ToString(), ": Comma (,) or Close Bracket (]) expected")); } } else if (t.Type == TokenType.Keyword) { if (t.Value == "true") return new Value(true); if (t.Value == "false") return new Value(false); if (t.Value == "null") return new Value(Type.Null); } throw new InvalidDataException(String.Concat("Parse error at line ", input.LineNum.ToString(), ", character ", input.CharNum.ToString(), ": Value expected")); } } internal enum TokenType { Nothing, OpenBrace, CloseBrace, OpenBracket, CloseBracket, Colon, Comma, String, Number, Keyword } internal struct Token { private TokenType mType; private String mValue; public Token(TokenType type, String value) { mType = type; mValue = value; } public TokenType Type { get { return mType; } } public String Value { get { return mValue; } } } internal class Tokenizer { private TextReader mInput; private StringBuilder mBuffer; private Int32 mIndex; private StringBuilder mScratch; private Int32 mLineNum; public Tokenizer(TextReader input) { mInput = input; mBuffer = new StringBuilder(256); mIndex = -1; mScratch = new StringBuilder(32); } public Int32 LineNum { get { return mLineNum; } } public Int32 CharNum { get { return mIndex + 1; } } // see http://www.json.org/ public Token ReadToken() { SkipWhiteSpace(); Nullable c = PeekChar(); if (!c.HasValue) return new Token(TokenType.Nothing, null); if (c.Value == '{') { SkipChar(); return new Token(TokenType.OpenBrace, "{"); } if (c.Value == '}') { SkipChar(); return new Token(TokenType.CloseBrace, "}"); } if (c.Value == '[') { SkipChar(); return new Token(TokenType.OpenBracket, "["); } if (c.Value == ']') { SkipChar(); return new Token(TokenType.CloseBracket, "]"); } if (c.Value == ':') { SkipChar(); return new Token(TokenType.Colon, ":"); } if (c.Value == ',') { SkipChar(); return new Token(TokenType.Comma, ","); } if (c.Value == '"') { mScratch.Length = 0; SkipChar(); while (((c = PeekChar()).HasValue) && (c.Value != '"')) { if (Char.IsControl(c.Value)) throw new InvalidDataException(); if (c.Value == '\\') { SkipChar(); c = ReadChar(); if (!c.HasValue) throw new InvalidDataException(); switch (c.Value) { case '"': mScratch.Append('"'); break; case '\\': mScratch.Append('\\'); break; case '/': mScratch.Append('/'); break; case 'b': mScratch.Append('\b'); break; case 'f': mScratch.Append('\f'); break; case 'n': mScratch.Append('\n'); break; case 'r': mScratch.Append('\r'); break; case 't': mScratch.Append('\t'); break; case 'u': Int32 n = 0; for (Int32 i = 0; i < 4; i++) { c = ReadChar(); Int32 v = HexValue(c); if (v == -1) throw new InvalidDataException(); n *= 16; n += v; } mScratch.Append((Char)n); break; default: throw new InvalidDataException(); } } else { mScratch.Append(c.Value); SkipChar(); } } if (!c.HasValue) throw new InvalidDataException(); SkipChar(); return new Token(TokenType.String, mScratch.ToString()); } if ((c.Value == '-') || (Char.IsDigit(c.Value))) { mScratch.Length = 0; SkipChar(); if (c.Value == '-') { mScratch.Append(c.Value); c = ReadChar(); if ((!c.HasValue) || (!Char.IsDigit(c.Value))) throw new InvalidDataException(); } mScratch.Append(c); if ((c.Value >= '1') && (c.Value <= '9')) { while (((c = PeekChar()).HasValue) && (Char.IsDigit(c.Value))) { mScratch.Append(c.Value); SkipChar(); } } if (((c = PeekChar()).HasValue) && (c.Value == '.')) { mScratch.Append(c.Value); SkipChar(); while (((c = PeekChar()).HasValue) && (Char.IsDigit(c.Value))) { mScratch.Append(c.Value); SkipChar(); } } if (((c = PeekChar()).HasValue) && ((c.Value == 'e') || (c.Value == 'E'))) { mScratch.Append(c.Value); SkipChar(); c = PeekChar(); if (!c.HasValue) throw new InvalidDataException(); if ((c.Value == '-') || (c.Value == '+')) { mScratch.Append(c.Value); SkipChar(); c = PeekChar(); if (!c.HasValue) throw new InvalidDataException(); } while (((c = PeekChar()).HasValue) && (Char.IsDigit(c.Value))) { mScratch.Append(c.Value); SkipChar(); } } return new Token(TokenType.Number, mScratch.ToString()); } if (Char.IsLetter(c.Value)) { mScratch.Length = 0; mScratch.Append(c.Value); SkipChar(); while (((c = PeekChar()).HasValue) && (Char.IsLetter(c.Value))) { mScratch.Append(c.Value); SkipChar(); } return new Token(TokenType.Keyword, mScratch.ToString()); } throw new InvalidDataException(); } private Int32 HexValue(Nullable character) { if (!character.HasValue) return -1; switch (character.Value) { 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': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return -1; } } internal Nullable PeekChar() { FillBuffer(); if (mIndex == -1) return null; return mBuffer[mIndex]; } private Nullable ReadChar() { FillBuffer(); if (mIndex == -1) return null; return mBuffer[mIndex++]; } private void SkipChar() { FillBuffer(); if (mIndex != -1) mIndex++; } internal void SkipWhiteSpace() { Nullable c; while (((c = PeekChar()).HasValue) && (Char.IsWhiteSpace(c.Value))) SkipChar(); } private void FillBuffer() { if ((mIndex == -1) || (mIndex >= mBuffer.Length)) { mBuffer.Length = 0; Int32 n; while ((n = mInput.Read()) != -1) { Char c = (Char)n; mBuffer.Append(c); if (c == '\n') { mLineNum++; break; } } mIndex = (mBuffer.Length == 0) ? -1 : 0; } } } }