| using System;␍␊ |
| using System.Collections;␍␊ |
| using System.Collections.Generic;␍␊ |
| using System.Globalization;␍␊ |
| using System.Text;␍␊ |
| ␍␊ |
| namespace Procurios.Public␍␊ |
| {␍␊ |
| /// <summary>␍␊ |
| /// This class encodes and decodes JSON strings.␍␊ |
| /// Spec. details, see http://www.json.org/␍␊ |
| ///␍␊ |
| /// JSON uses Arrays and Objects. These correspond here to the datatypes List and Dictionary.␍␊ |
| /// All numbers are parsed to doubles.␍␊ |
| /// </summary>␍␊ |
| public class JSON␍␊ |
| {␍␊ |
| public const int TOKEN_NONE = 0;␍␊ |
| public const int TOKEN_CURLY_OPEN = 1;␍␊ |
| public const int TOKEN_CURLY_CLOSE = 2;␍␊ |
| public const int TOKEN_SQUARED_OPEN = 3;␍␊ |
| public const int TOKEN_SQUARED_CLOSE = 4;␍␊ |
| public const int TOKEN_COLON = 5;␍␊ |
| public const int TOKEN_COMMA = 6;␍␊ |
| public const int TOKEN_STRING = 7;␍␊ |
| public const int TOKEN_NUMBER = 8;␍␊ |
| public const int TOKEN_TRUE = 9;␍␊ |
| public const int TOKEN_FALSE = 10;␍␊ |
| public const int TOKEN_NULL = 11;␍␊ |
| ␍␊ |
| private const int BUILDER_CAPACITY = 2000;␍␊ |
| ␍␊ |
| /// <summary>␍␊ |
| /// Parses the string json into a value␍␊ |
| /// </summary>␍␊ |
| /// <param name="json">A JSON string.</param>␍␊ |
| /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns>␍␊ |
| public static dynamic JsonDecode(string json)␍␊ |
| {␍␊ |
| bool success = true;␍␊ |
| ␍␊ |
| return JsonDecode(json, ref success);␍␊ |
| }␍␊ |
| ␍␊ |
| /// <summary>␍␊ |
| /// Parses the string json into a value; and fills 'success' with the successfullness of the parse.␍␊ |
| /// </summary>␍␊ |
| /// <param name="json">A JSON string.</param>␍␊ |
| /// <param name="success">Successful parse?</param>␍␊ |
| /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns>␍␊ |
| public static dynamic JsonDecode(string json, ref bool success)␍␊ |
| {␍␊ |
| success = true;␍␊ |
| if (json != null)␍␊ |
| {␍␊ |
| char[] charArray = json.ToCharArray();␍␊ |
| int index = 0;␍␊ |
| dynamic value = ParseValue(charArray, ref index, ref success);␍␊ |
| return value;␍␊ |
| }␍␊ |
| else␍␊ |
| {␍␊ |
| return null;␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| /// <summary>␍␊ |
| /// Converts a Hashtable / ArrayList object into a JSON string␍␊ |
| /// </summary>␍␊ |
| /// <param name="json">A Hashtable / ArrayList</param>␍␊ |
| /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>␍␊ |
| public static string JsonEncode(object json)␍␊ |
| {␍␊ |
| StringBuilder builder = new StringBuilder(BUILDER_CAPACITY);␍␊ |
| bool success = SerializeValue(json, builder);␍␊ |
| return (success ? builder.ToString() : null);␍␊ |
| }␍␊ |
| ␍␊ |
| protected static dynamic ParseObject(char[] json, ref int index, ref bool success)␍␊ |
| {␍␊ |
| Dictionary<dynamic, dynamic> table = new Dictionary<dynamic, dynamic>();␍␊ |
| int token;␍␊ |
| ␍␊ |
| // {␍␊ |
| NextToken(json, ref index);␍␊ |
| ␍␊ |
| bool done = false;␍␊ |
| while (!done)␍␊ |
| {␍␊ |
| token = LookAhead(json, index);␍␊ |
| if (token == JSON.TOKEN_NONE)␍␊ |
| {␍␊ |
| success = false;␍␊ |
| return null;␍␊ |
| }␍␊ |
| else if (token == JSON.TOKEN_COMMA)␍␊ |
| {␍␊ |
| NextToken(json, ref index);␍␊ |
| }␍␊ |
| else if (token == JSON.TOKEN_CURLY_CLOSE)␍␊ |
| {␍␊ |
| 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 != JSON.TOKEN_COLON)␍␊ |
| {␍␊ |
| 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 dynamic ParseArray(char[] json, ref int index, ref bool success)␍␊ |
| {␍␊ |
| List<dynamic> array = new List<dynamic>();␍␊ |
| ␍␊ |
| // [␍␊ |
| NextToken(json, ref index);␍␊ |
| ␍␊ |
| bool done = false;␍␊ |
| while (!done)␍␊ |
| {␍␊ |
| int token = LookAhead(json, index);␍␊ |
| if (token == JSON.TOKEN_NONE)␍␊ |
| {␍␊ |
| success = false;␍␊ |
| return null;␍␊ |
| }␍␊ |
| else if (token == JSON.TOKEN_COMMA)␍␊ |
| {␍␊ |
| NextToken(json, ref index);␍␊ |
| }␍␊ |
| else if (token == JSON.TOKEN_SQUARED_CLOSE)␍␊ |
| {␍␊ |
| 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 dynamic ParseValue(char[] json, ref int index, ref bool success)␍␊ |
| {␍␊ |
| switch (LookAhead(json, index))␍␊ |
| {␍␊ |
| case JSON.TOKEN_STRING:␍␊ |
| return ParseString(json, ref index, ref success);␍␊ |
| case JSON.TOKEN_NUMBER:␍␊ |
| return ParseNumber(json, ref index, ref success);␍␊ |
| case JSON.TOKEN_CURLY_OPEN:␍␊ |
| return ParseObject(json, ref index, ref success);␍␊ |
| case JSON.TOKEN_SQUARED_OPEN:␍␊ |
| return ParseArray(json, ref index, ref success);␍␊ |
| case JSON.TOKEN_TRUE:␍␊ |
| NextToken(json, ref index);␍␊ |
| return true;␍␊ |
| case JSON.TOKEN_FALSE:␍␊ |
| NextToken(json, ref index);␍␊ |
| return false;␍␊ |
| case JSON.TOKEN_NULL:␍␊ |
| NextToken(json, ref index);␍␊ |
| return null;␍␊ |
| case JSON.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 = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint)))␍␊ |
| {␍␊ |
| return "";␍␊ |
| }␍␊ |
| // convert the integer codepoint to a unicode char and add to string␍␊ |
| s.Append(Char.ConvertFromUtf32((int)codePoint));␍␊ |
| // skip 4 chars␍␊ |
| index += 4;␍␊ |
| }␍␊ |
| else␍␊ |
| {␍␊ |
| break;␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| }␍␊ |
| else␍␊ |
| {␍␊ |
| s.Append(c);␍␊ |
| }␍␊ |
| ␍␊ |
| }␍␊ |
| ␍␊ |
| if (!complete)␍␊ |
| {␍␊ |
| success = false;␍␊ |
| return null;␍␊ |
| }␍␊ |
| ␍␊ |
| return s.ToString();␍␊ |
| }␍␊ |
| ␍␊ |
| protected static double ParseNumber(char[] json, ref int index, ref bool success)␍␊ |
| {␍␊ |
| EatWhitespace(json, ref index);␍␊ |
| ␍␊ |
| int lastIndex = GetLastIndexOfNumber(json, index);␍␊ |
| int charLength = (lastIndex - index) + 1;␍␊ |
| ␍␊ |
| double number;␍␊ |
| success = Double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);␍␊ |
| ␍␊ |
| index = lastIndex + 1;␍␊ |
| return number;␍␊ |
| }␍␊ |
| ␍␊ |
| 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 int LookAhead(char[] json, int index)␍␊ |
| {␍␊ |
| int saveIndex = index;␍␊ |
| return NextToken(json, ref saveIndex);␍␊ |
| }␍␊ |
| ␍␊ |
| protected static int NextToken(char[] json, ref int index)␍␊ |
| {␍␊ |
| EatWhitespace(json, ref index);␍␊ |
| ␍␊ |
| if (index == json.Length)␍␊ |
| {␍␊ |
| return JSON.TOKEN_NONE;␍␊ |
| }␍␊ |
| ␍␊ |
| char c = json[index];␍␊ |
| index++;␍␊ |
| switch (c)␍␊ |
| {␍␊ |
| case '{':␍␊ |
| return JSON.TOKEN_CURLY_OPEN;␍␊ |
| case '}':␍␊ |
| return JSON.TOKEN_CURLY_CLOSE;␍␊ |
| case '[':␍␊ |
| return JSON.TOKEN_SQUARED_OPEN;␍␊ |
| case ']':␍␊ |
| return JSON.TOKEN_SQUARED_CLOSE;␍␊ |
| case ',':␍␊ |
| return JSON.TOKEN_COMMA;␍␊ |
| case '"':␍␊ |
| return JSON.TOKEN_STRING;␍␊ |
| case '0':␍␊ |
| case '1':␍␊ |
| case '2':␍␊ |
| case '3':␍␊ |
| case '4':␍␊ |
| case '5':␍␊ |
| case '6':␍␊ |
| case '7':␍␊ |
| case '8':␍␊ |
| case '9':␍␊ |
| case '-':␍␊ |
| return JSON.TOKEN_NUMBER;␍␊ |
| case ':':␍␊ |
| return JSON.TOKEN_COLON;␍␊ |
| }␍␊ |
| 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 JSON.TOKEN_FALSE;␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| // true␍␊ |
| if (remainingLength >= 4)␍␊ |
| {␍␊ |
| if (json[index] == 't' &&␍␊ |
| json[index + 1] == 'r' &&␍␊ |
| json[index + 2] == 'u' &&␍␊ |
| json[index + 3] == 'e')␍␊ |
| {␍␊ |
| index += 4;␍␊ |
| return JSON.TOKEN_TRUE;␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| // null␍␊ |
| if (remainingLength >= 4)␍␊ |
| {␍␊ |
| if (json[index] == 'n' &&␍␊ |
| json[index + 1] == 'u' &&␍␊ |
| json[index + 2] == 'l' &&␍␊ |
| json[index + 3] == 'l')␍␊ |
| {␍␊ |
| index += 4;␍␊ |
| return JSON.TOKEN_NULL;␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| return JSON.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 ((value is Boolean) && ((Boolean)value == true))␍␊ |
| {␍␊ |
| builder.Append("true");␍␊ |
| }␍␊ |
| else if ((value is Boolean) && ((Boolean)value == false))␍␊ |
| {␍␊ |
| builder.Append("false");␍␊ |
| }␍␊ |
| else if (value is ValueType)␍␊ |
| {␍␊ |
| // thanks to ritchie for pointing out ValueType to me␍␊ |
| success = SerializeNumber(Convert.ToDouble(value), builder);␍␊ |
| }␍␊ |
| else if (value == null)␍␊ |
| {␍␊ |
| builder.Append("null");␍␊ |
| }␍␊ |
| else␍␊ |
| {␍␊ |
| success = false;␍␊ |
| }␍␊ |
| return success;␍␊ |
| }␍␊ |
| ␍␊ |
| protected static bool SerializeObject(Hashtable anObject, StringBuilder builder)␍␊ |
| {␍␊ |
| builder.Append("{");␍␊ |
| ␍␊ |
| IDictionaryEnumerator e = anObject.GetEnumerator();␍␊ |
| bool first = true;␍␊ |
| while (e.MoveNext())␍␊ |
| {␍␊ |
| string key = e.Key.ToString();␍␊ |
| object value = e.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);␍␊ |
| if ((codepoint >= 32) && (codepoint <= 126))␍␊ |
| {␍␊ |
| builder.Append(c);␍␊ |
| }␍␊ |
| else␍␊ |
| {␍␊ |
| builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0'));␍␊ |
| }␍␊ |
| }␍␊ |
| }␍␊ |
| ␍␊ |
| builder.Append("\"");␍␊ |
| return true;␍␊ |
| }␍␊ |
| ␍␊ |
| protected static bool SerializeNumber(double number, StringBuilder builder)␍␊ |
| {␍␊ |
| builder.Append(Convert.ToString(number, CultureInfo.InvariantCulture));␍␊ |
| return true;␍␊ |
| }␍␊ |
| }␍␊ |
| }␍␊ |