Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / Markdown

fastJSON - Smallest, Fastest Polymorphic JSON Serializer

Rate me:
Please Sign up or sign in to vote.
4.91/5 (412 votes)
23 Jan 2021CPOL37 min read 5.6M   63.8K   984  
In this article I demonstrate why fastJSON is the smallest, fastest polymorphic JSON serializer (with Silverlight4, MonoDroid and .NET core support)
Here we look at: the what and why of JSON, features of this implementation, some of the JSON alternatives that I have personally used, using the code, and performance tests.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace fastJSON
{
	/// <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>
	internal class JsonParser
	{
		private const int TOKEN_NONE = 0;
		private const int TOKEN_CURLY_OPEN = 1;
		private const int TOKEN_CURLY_CLOSE = 2;
		private const int TOKEN_SQUARED_OPEN = 3;
		private const int TOKEN_SQUARED_CLOSE = 4;
		private const int TOKEN_COLON = 5;
		private const int TOKEN_COMMA = 6;
		private const int TOKEN_STRING = 7;
		private const int TOKEN_NUMBER = 8;
		private const int TOKEN_TRUE = 9;
		private const int TOKEN_FALSE = 10;
		private const int TOKEN_NULL = 11;

		
		/// <summary>
		/// Parses the string json into a value
		/// </summary>
		/// <param name="json">A JSON string.</param>
		/// <returns>An ArrayList, a dictionary, a double, a string, null, true, or false</returns>
		internal 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>
		private 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;
			}
		}
		

		private Dictionary<string,object> ParseObject(char[] json, ref int index, ref bool success)
		{
			Dictionary<string,object> table = new Dictionary<string, object>();
			int token;

			// {
			NextToken(json, ref index);

			bool done = false;
			while (!done) {
				token = LookAhead(json, index);
				if (token == TOKEN_NONE) {
					success = false;
					return null;
				} else if (token == TOKEN_COMMA) {
					NextToken(json, ref index);
				} else if (token == 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 != 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;
		}

		private 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 == TOKEN_NONE) {
					success = false;
					return null;
				} else if (token == TOKEN_COMMA) {
					NextToken(json, ref index);
				} else if (token == 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;
		}

		private object ParseValue(char[] json, ref int index, ref bool success)
		{
			switch (LookAhead(json, index)) {
				case TOKEN_NUMBER:
					return ParseNumber(json, ref index, ref success);
				case TOKEN_STRING:
					return ParseString(json, ref index, ref success);
				case TOKEN_CURLY_OPEN:
					return ParseObject(json, ref index, ref success);
				case TOKEN_SQUARED_OPEN:
					return ParseArray(json, ref index, ref success);
				case TOKEN_TRUE:
					NextToken(json, ref index);
					return true;
				case TOKEN_FALSE:
					NextToken(json, ref index);
					return false;
				case TOKEN_NULL:
					NextToken(json, ref index);
					return null;
				case TOKEN_NONE:
					break;
			}

			success = false;
			return null;
		}

		private StringBuilder s = new StringBuilder();
		private string ParseString(char[] json, ref int index, ref bool success)
		{
            s.Length = 0;
			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 != '\\')
					s.Append(c);
				else
				{
					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;
					}
				}
			}

			if (!complete) {
				success = false;
				return null;
			}

			return s.ToString();
		}

		private string ParseNumber(char[] json, ref int index, ref bool success)
		{
			EatWhitespace(json, ref index);

			int lastIndex = GetLastIndexOfNumber(json, index);
			int charLength = (lastIndex - index) + 1;

			string number = new string(json,index,charLength);
			
			success = true;
			
			index = lastIndex + 1;
			return number;
		}

		private int GetLastIndexOfNumber(char[] json, int index)
		{
			int lastIndex=index;
			int len = json.Length;
			while (lastIndex<len)
			{
				char c = json[lastIndex];
				if((c>='0' && c<='9') || c=='+' || c=='-' || c=='.' || c=='e' || c=='E')
					lastIndex++;
				else
					break;
			}
			return lastIndex-1;
		}

		private void EatWhitespace(char[] json, ref int index)
		{
			int len = json.Length;
			while(index<len)
			{
				char c = json[index];
				if(c == '\t' || c=='\r' || c== '\n' || c== ' ')
					index++;
				else
					break;
			}
		}

		private int LookAhead(char[] json, int index)
		{
			int saveIndex = index;
			return NextToken(json, ref saveIndex);
		}

		private int NextToken(char[] json, ref int index)
		{
			EatWhitespace(json, ref index);

			if (index == json.Length) {
				return TOKEN_NONE;
			}
			
			char c = json[index];
			index++;
			switch (c) {
				case '{':
					return TOKEN_CURLY_OPEN;
				case '}':
					return TOKEN_CURLY_CLOSE;
				case '[':
					return TOKEN_SQUARED_OPEN;
				case ']':
					return TOKEN_SQUARED_CLOSE;
				case ',':
					return TOKEN_COMMA;
				case '"':
					return TOKEN_STRING;
				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': case '8': case '9':
				case '-':
					return TOKEN_NUMBER;
				case ':':
					return TOKEN_COLON;
			}
			index--;

			int remainingLength = json.Length - index;
			char c1 = json[index];
			char c2 = json[index+1];
			char c3 = json[index+2];
			char c4 = json[index+3];
			char c5 = json[index+4];

			// false
			if (remainingLength >= 5) {
				if (c1 == 'f' && c2 == 'a' && c3 == 'l' && c4 == 's' && c5 == 'e') {
					index += 5;
					return TOKEN_FALSE;
				}
			}

			// true
			if (remainingLength >= 4) {
				if (c1 == 't' && c2 == 'r' && c3 == 'u' && c4 == 'e') {
					index += 4;
					return TOKEN_TRUE;
				}
			}

			// null
			if (remainingLength >= 4) {
				if (c1 == 'n' && c2 == 'u' && c3 == 'l' && c4 == 'l') {
					index += 4;
					return TOKEN_NULL;
				}
			}

			return TOKEN_NONE;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect -
United Kingdom United Kingdom
Mehdi first started programming when he was 8 on BBC+128k machine in 6512 processor language, after various hardware and software changes he eventually came across .net and c# which he has been using since v1.0.
He is formally educated as a system analyst Industrial engineer, but his programming passion continues.

* Mehdi is the 5th person to get 6 out of 7 Platinum's on Code-Project (13th Jan'12)
* Mehdi is the 3rd person to get 7 out of 7 Platinum's on Code-Project (26th Aug'16)

Comments and Discussions