using System;␊ |
using System.Collections;␊ |
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 ArrayList and Hashtable.␊ |
␉/// 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 object 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 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;␊ |
␉␉␉}␊ |
␉␉}␊ |
␊ |
␉␉/// <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 Hashtable ParseObject(char[] json, ref int index, ref bool success)␊ |
␉␉{␊ |
␉␉␉Hashtable table = new Hashtable();␊ |
␉␉␉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 ArrayList ParseArray(char[] json, ref int index, ref bool success)␊ |
␉␉{␊ |
␉␉␉ArrayList array = new ArrayList();␊ |
␊ |
␉␉␉// [␊ |
␉␉␉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 object 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;␊ |
␉␉}␊ |
␉}␊ |
}␊ |
␊ |