using System; using System.Collections; using System.Globalization; using System.Text; using IndianaJones.NETMF.String; using IndianaJones.NETMF.Integer; namespace IndianaJones.NETMF.Json { /// /// Parses JSON strings into a Hashtable. The Hashtable contains one or more key/value pairs /// (DictionaryEntry objects). Each key is the name of a property that (hopefully) exists /// in the class object that it represents. Each value is one of the following: /// Hastable - Another list of one or more DictionaryEntry objects, essentially representing /// a property that is another class. /// ArrayList - An array of one or more objects, which themselves can be one of the items /// enumerated in this list. /// Value Type - an actual value, such as a string, int, bool, Guid, DateTime, etc /// public class JsonParser { public enum Token { None = 0, ObjectBegin, // { ObjectEnd, // } ArrayBegin, // [ ArrayEnd, // ] PropertySeparator, // : ItemsSeparator, // , StringType, // " <-- string of characters NumberType, // 0-9 <-- number, fixed or floating point BooleanTrue, // true BooleanFalse, // false NullType // null } private const int BUILDER_CAPACITY = 2000; /// /// Parses the string json into a value /// /// A JSON string. /// An ArrayList, a Hashtable, a double, a string, null, true, or false public static object JsonDecode(string json) { bool success = true; return JsonDecode(json, ref success); } /// /// Parses the string json into a value; and fills 'success' with the successfullness of the parse. /// /// A JSON string. /// Successful parse? /// An ArrayList, a Hashtable, a double, a string, null, true, or false public static object JsonDecode(string json, ref bool success) { success = true; if (json != null) { char[] charArray = json.ToCharArray(); int index = 0; object value = ParseValue(charArray, ref index, ref success); return value; } else { return null; } } protected static Hashtable ParseObject(char[] json, ref int index, ref bool success) { Hashtable table = new Hashtable(); Token token; // { NextToken(json, ref index); bool done = false; while (!done) { token = LookAhead(json, index); if (token == JsonParser.Token.None) { success = false; return null; } else if (token == JsonParser.Token.ItemsSeparator) { NextToken(json, ref index); } else if (token == JsonParser.Token.ObjectEnd) { NextToken(json, ref index); return table; } else { // name string name = ParseString(json, ref index, ref success); if (!success) { success = false; return null; } // : token = NextToken(json, ref index); if (token != JsonParser.Token.PropertySeparator) { success = false; return null; } // value object value = ParseValue(json, ref index, ref success); if (!success) { success = false; return null; } table[name] = value; } } return table; } protected static ArrayList ParseArray(char[] json, ref int index, ref bool success) { ArrayList array = new ArrayList(); // [ NextToken(json, ref index); bool done = false; while (!done) { Token token = LookAhead(json, index); if (token == JsonParser.Token.None) { success = false; return null; } else if (token == JsonParser.Token.ItemsSeparator) { NextToken(json, ref index); } else if (token == JsonParser.Token.ArrayEnd) { NextToken(json, ref index); break; } else { object value = ParseValue(json, ref index, ref success); if (!success) { return null; } array.Add(value); } } return array; } protected static object ParseValue(char[] json, ref int index, ref bool success) { switch (LookAhead(json, index)) { case JsonParser.Token.StringType: return ParseString(json, ref index, ref success); case JsonParser.Token.NumberType: return ParseNumber(json, ref index, ref success); case JsonParser.Token.ObjectBegin: return ParseObject(json, ref index, ref success); case JsonParser.Token.ArrayBegin: return ParseArray(json, ref index, ref success); case JsonParser.Token.BooleanTrue: NextToken(json, ref index); return true; case JsonParser.Token.BooleanFalse: NextToken(json, ref index); return false; case JsonParser.Token.NullType: NextToken(json, ref index); return null; case JsonParser.Token.None: break; } success = false; return null; } protected static string ParseString(char[] json, ref int index, ref bool success) { StringBuilder s = new StringBuilder(BUILDER_CAPACITY); char c; EatWhitespace(json, ref index); // " c = json[index++]; bool complete = false; while (!complete) { if (index == json.Length) { break; } c = json[index++]; if (c == '"') { complete = true; break; } else if (c == '\\') { if (index == json.Length) { break; } c = json[index++]; if (c == '"') { s.Append('"'); } else if (c == '\\') { s.Append('\\'); } else if (c == '/') { s.Append('/'); } else if (c == 'b') { s.Append('\b'); } else if (c == 'f') { s.Append('\f'); } else if (c == 'n') { s.Append('\n'); } else if (c == 'r') { s.Append('\r'); } else if (c == 't') { s.Append('\t'); } else if (c == 'u') { int remainingLength = json.Length - index; if (remainingLength >= 4) { // parse the 32 bit hex into an integer codepoint uint codePoint; if (!(success = UInt32Extensions.TryParse(new string(json, index, 4), NumberStyle.Hexadecimal, out codePoint))) { return ""; } // convert the integer codepoint to a unicode char and add to string s.Append(CharExtensions.ConvertFromUtf32((int)codePoint)); // skip 4 chars index += 4; } else { break; } } } else { s.Append(c); } } if (!complete) { success = false; return null; } return s.ToString(); } /// /// Determines the type of number (int, double, etc) and returns an object /// containing that value. /// /// /// /// /// protected static object ParseNumber(char[] json, ref int index, ref bool success) { EatWhitespace(json, ref index); int lastIndex = GetLastIndexOfNumber(json, index); int charLength = (lastIndex - index) + 1; // We now have the number as a string. Parse it to determine the type of number. string value = new string(json, index, charLength); // Since the Json doesn't contain the Type of the property, and since multiple number // values can fit in the various Types (e.g. 33 decimal fits in an Int16, UInt16, // Int32, UInt32, Int64, and UInt64), we need to be a bit smarter in how we deal with // the size of the number, and also the case (negative or positive). object result = null; string dot = CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator; string comma = CultureInfo.CurrentUICulture.NumberFormat.NumberGroupSeparator; string minus = CultureInfo.CurrentUICulture.NumberFormat.NegativeSign; string plus = CultureInfo.CurrentUICulture.NumberFormat.PositiveSign; if (value.Contains(dot) || value.Contains(comma) || value.Contains("e") || value.Contains("E")) { // We have either a double or a float. Force it to be a double // and let the deserializer unbox it into the proper size. result = Double.Parse(new string(json, index, charLength)); } else { NumberStyle style = NumberStyle.Decimal; if(value.StartsWith("0x") || (value.IndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' }) >= 0)) { style = NumberStyle.Hexadecimal; } // If it's an integer, force it to either signed or unsigned 64-bit. // The deserializer will then do unboxing to fit it into the proper size. if(value.StartsWith(minus) || value.StartsWith(plus)) { result = Int64Extensions.Parse(value, style); } else { result = UInt64Extensions.Parse(value, style); } } index = lastIndex + 1; return result; } protected static int GetLastIndexOfNumber(char[] json, int index) { int lastIndex; for (lastIndex = index; lastIndex < json.Length; lastIndex++) { if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) { break; } } return lastIndex - 1; } protected static void EatWhitespace(char[] json, ref int index) { for (; index < json.Length; index++) { if (" \t\n\r".IndexOf(json[index]) == -1) { break; } } } protected static Token LookAhead(char[] json, int index) { int saveIndex = index; return NextToken(json, ref saveIndex); } protected static Token NextToken(char[] json, ref int index) { EatWhitespace(json, ref index); if (index == json.Length) { return JsonParser.Token.None; } char c = json[index]; index++; switch (c) { case '{': return JsonParser.Token.ObjectBegin; case '}': return JsonParser.Token.ObjectEnd; case '[': return JsonParser.Token.ArrayBegin; case ']': return JsonParser.Token.ArrayEnd; case ',': return JsonParser.Token.ItemsSeparator; case '"': return JsonParser.Token.StringType; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': return JsonParser.Token.NumberType; case ':': return JsonParser.Token.PropertySeparator; } index--; int remainingLength = json.Length - index; // false if (remainingLength >= 5) { if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') { index += 5; return JsonParser.Token.BooleanFalse; } } // true if (remainingLength >= 4) { if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { index += 4; return JsonParser.Token.BooleanTrue; } } // null if (remainingLength >= 4) { if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { index += 4; return JsonParser.Token.NullType; } } return JsonParser.Token.None; } protected static bool SerializeValue(object value, StringBuilder builder) { bool success = true; if (value is string) { success = SerializeString((string)value, builder); } else if (value is Hashtable) { success = SerializeObject((Hashtable)value, builder); } else if (value is ArrayList) { success = SerializeArray((ArrayList)value, builder); } else if (IsNumeric(value)) { success = SerializeNumber(Convert.ToDouble(value.ToString()), builder); } else if ((value is Boolean) && ((Boolean)value == true)) { builder.Append("true"); } else if ((value is Boolean) && ((Boolean)value == false)) { builder.Append("false"); } else if (value == null) { builder.Append("null"); } else { success = false; } return success; } protected static bool SerializeObject(Hashtable anObject, StringBuilder builder) { builder.Append("{"); IEnumerator e = anObject.GetEnumerator(); //Hashtable e = anObject; bool first = true; while (e.MoveNext()) { DictionaryEntry d = e.Current as DictionaryEntry; string key = d.Key.ToString(); object value = d.Value; if (!first) { builder.Append(", "); } SerializeString(key, builder); builder.Append(":"); if (!SerializeValue(value, builder)) { return false; } first = false; } builder.Append("}"); return true; } protected static bool SerializeArray(ArrayList anArray, StringBuilder builder) { builder.Append("["); bool first = true; for (int i = 0; i < anArray.Count; i++) { object value = anArray[i]; if (!first) { builder.Append(", "); } if (!SerializeValue(value, builder)) { return false; } first = false; } builder.Append("]"); return true; } protected static bool SerializeString(string aString, StringBuilder builder) { builder.Append("\""); char[] charArray = aString.ToCharArray(); for (int i = 0; i < charArray.Length; i++) { char c = charArray[i]; if (c == '"') { builder.Append("\\\""); } else if (c == '\\') { builder.Append("\\\\"); } else if (c == '\b') { builder.Append("\\b"); } else if (c == '\f') { builder.Append("\\f"); } else if (c == '\n') { builder.Append("\\n"); } else if (c == '\r') { builder.Append("\\r"); } else if (c == '\t') { builder.Append("\\t"); } else { int codepoint = Convert.ToInt32(c.ToString()); if ((codepoint >= 32) && (codepoint <= 126)) { builder.Append(c); } else { string value = Int32Extensions.ToHexString(codepoint); builder.Append("\\u" + StringExtensions.PadLeft(value, 4, '0')); } } } builder.Append("\""); return true; } protected static bool SerializeNumber(double number, StringBuilder builder) { builder.Append(DoubleExtensions.ToHexString(number/*, CultureInfo.CurrentUICulture*/)); return true; } /// /// Determines if a given object is numeric in any way /// (can be integer, double, null, etc). /// /// Thanks to mtighe for pointing out Double.TryParse to me. /// protected static bool IsNumeric(object o) { double result; return (o == null) ? false : DoubleExtensions.TryParse(o.ToString(), out result); } } /// /// A Json Object. /// Programmed by Huysentruit Wouter /// See the Json.ToJson method for more information. /// public class JsonObject : Hashtable { /// /// Convert the object to its JSON representation. /// /// A string containing the JSON representation of the object. public override string ToString() { string result = ""; string[] keys = new string[Count]; object[] values = new object[Count]; Keys.CopyTo(keys, 0); Values.CopyTo(values, 0); for (int i = 0; i < Count; i++) { if (result.Length > 0) { result += ", "; } // If this string is already JSON'd, as denoted by start of object { // or start of array [, then use it as-is. Otherwise, encode it to JSON string value = string.Empty; char v = values[i].ToString()[0]; if ((v == '{') || (v == '[')) { value = values[i].ToString(); } else { value = JsonPrimitives.Serialize(values[i]); } if (value == null) { continue; } result += "\"" + keys[i] + "\""; result += ": "; result += value; } return "{" + result + "}"; } } /// /// A Json Array. /// Programmed by Huysentruit Wouter /// See the Json.ToJson method for more information. /// public class JsonArray : ArrayList { /// /// Convert the array to its JSON representation. /// /// A string containing the JSON representation of the array. public override string ToString() { string[] parts = new string[Count]; for (int i = 0; i < Count; i++) { // Encapsulate in quotes if not a JSON object or not already in quotes char c = this[i].ToString()[0]; if (c == '{' || c == '[' || c == '"') { parts[i] = this[i].ToString(); } else { parts[i] = "\"" + this[i].ToString() + "\""; } } string result = ""; foreach (string part in parts) { if (result.Length > 0) { result += ", "; } result += part; } return "[" + result + "]"; } } }