Skip to content

Instantly share code, notes, and snippets.

@HamedFathi
Forked from yasar11732/JSON.cs
Created December 9, 2019 06:06
Show Gist options
  • Save HamedFathi/1dab4887572270917516e0016001cec1 to your computer and use it in GitHub Desktop.
Save HamedFathi/1dab4887572270917516e0016001cec1 to your computer and use it in GitHub Desktop.
JSON parser and stringifier implemented in C# (Recursive Descent Parser)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MiniWebFramework
{
public enum JSONValueType
{
NULL,
TRUE,
FALSE,
NUMBER,
STRING,
ARRAY,
OBJECT
}
public class JSONValue
{
public JSONValueType type { get; private set; }
public object value { get; private set; }
public JSONValue(JSONValueType t, object v)
{
type = t;
value = v;
}
}
public static class JSON
{
private static readonly char[] positive_digits = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private static readonly char[] all_digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private static readonly char[] white_space = new char[] { ' ', '\t', '\n', '\r' };
private static readonly char[] string_breakers = new char[] { '"', '\\', '\r', '\n', '\t', '\b', '\f'};
private static readonly char[] hexdigit = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'A', 'B', 'C', 'D', 'E', 'F'
};
private class KVPair
{
public string key;
public JSONValue value;
}
private class ParseContext
{
private static readonly char EOT = '\x04';
private char[] data;
int position;
int cap;
public ParseContext(char[] _data)
{
data = _data;
cap = _data.Length;
position = 0;
}
public bool isEOT()
{
return !(position < cap);
}
public char Peek()
{
if(position < cap)
return data[position];
return EOT;
}
public char Read()
{
if (position < cap)
return data[position++];
return EOT;
}
public int Tell()
{
return position;
}
public void Seek(int p)
{
position = p;
}
public string StringFrom(int start)
{
int strlen = position - start;
char[] segment = new char[strlen];
for(var i = 0; i < strlen; i++)
{
segment[i] = data[start + i];
}
return new string(segment);
}
}
private static bool parse_literal(ParseContext p, string literal)
{
bool isMatch = true;
int current = p.Tell();
foreach(var c in literal)
{
if(c != p.Read())
{
isMatch = false;
break;
}
}
if (!isMatch)
p.Seek(current);
return isMatch;
}
private static bool parse_char(ParseContext p, char c)
{
if(c == p.Peek())
{
p.Read();
return true;
}
return false;
}
private static bool parse_any(ParseContext p, char[] options)
{
bool isMatch = false;
char next_char = p.Peek();
for(var i = 0; i < options.Length; i++)
{
if(options[i] == next_char)
{
isMatch = true;
p.Read();
break;
}
}
return isMatch;
}
private static bool parse_ws(ParseContext p)
{
int num_matches = 0;
while(parse_any(p, white_space))
{
num_matches++;
}
return num_matches > 0;
}
private static JSONValue parse_json_null(ParseContext p)
{
if (parse_literal(p, "null"))
return new JSONValue(JSONValueType.NULL, null);
return null;
}
private static JSONValue parse_json_true(ParseContext p)
{
if (parse_literal(p, "true"))
return new JSONValue(JSONValueType.TRUE, null);
return null;
}
private static JSONValue parse_json_false(ParseContext p)
{
if (parse_literal(p, "false"))
return new JSONValue(JSONValueType.FALSE, null);
return null;
}
private static JSONValue parse_json_number(ParseContext p)
{
int start = p.Tell();
parse_char(p, '-');
if(!parse_int(p))
{
p.Seek(start);
return null;
}
parse_frac(p);
parse_exponent(p);
return new JSONValue(JSONValueType.NUMBER, Convert.ToDouble(p.StringFrom(start)));
}
private static bool parse_exponent(ParseContext p)
{
int start = p.Tell();
if (!parse_any(p, new char[] { 'e', 'E' }))
return false;
parse_any(p, new char[] { '-', '+' });
if (!parse_any(p, all_digits))
{
p.Seek(start);
return false;
}
while(parse_any(p, all_digits))
{
}
return true;
}
private static bool parse_frac(ParseContext p)
{
int start = p.Tell();
if (!parse_char(p, '.'))
return false;
if (!parse_any(p, all_digits))
{
p.Seek(start);
return false;
}
while(parse_any(p, all_digits))
{
}
return true;
}
private static bool parse_int(ParseContext p)
{
if(parse_char(p, '0'))
{
return true;
}
if(!parse_any(p, positive_digits))
{
return false;
}
while(parse_any(p, all_digits))
{
}
return true;
}
private static string parse_string_token(ParseContext p)
{
int start = p.Tell();
if (!parse_char(p, '\"'))
return null;
while (parse_normal_char(p) || parse_escaped_char(p))
{
continue;
}
if(p.Peek() != '"')
{
p.Seek(start);
return null;
}
var result = p.StringFrom(start + 1); // +1 skip initial double quote
p.Read(); // consume ending double quote
return Regex.Unescape(result);
}
private static JSONValue parse_json_string(ParseContext p)
{
string s;
if ((s = parse_string_token(p)) != null)
return new JSONValue(JSONValueType.STRING, s);
else
return null;
}
private static bool parse_escaped_char(ParseContext p)
{
int start = p.Tell();
if (!parse_char(p, '\\'))
return false;
if (p.isEOT())
{
p.Seek(start);
return false;
}
var c = p.Read();
if (c != 'u')
return true;
if(
!parse_any(p, hexdigit) ||
!parse_any(p, hexdigit) ||
!parse_any(p, hexdigit) ||
!parse_any(p, hexdigit)
)
{
p.Seek(start);
return false;
}
return true;
}
private static bool parse_normal_char(ParseContext p)
{
var c = p.Peek();
if (!shouldescape(c))
{
p.Read();
return true;
} else
{
return false;
}
}
private static JSONValue parse_json_array(ParseContext p)
{
int start = p.Tell();
if (!parse_char(p, '['))
return null;
parse_ws(p); // skip optional space
List<JSONValue> elems = parse_elems(p);
if (!parse_char(p, ']'))
{
p.Seek(start);
return null;
}
return new JSONValue(JSONValueType.ARRAY, elems);
}
private static List<JSONValue> parse_elems(ParseContext p)
{
var elems = new List<JSONValue>();
var elem = parse_jsonvalue(p);
while(elem != null)
{
elems.Add(elem);
if (!parse_char(p, ','))
break;
elem = parse_jsonvalue(p);
}
return elems;
}
private static JSONValue parse_json_object(ParseContext p)
{
int start = p.Tell();
if (!parse_char(p, '{'))
return null;
parse_ws(p); // skip optional space
Dictionary<string, JSONValue> elems = parse_kvpairs(p);
if (!parse_char(p, '}'))
{
p.Seek(start);
return null;
}
return new JSONValue(JSONValueType.OBJECT, elems);
}
private static Dictionary<string, JSONValue> parse_kvpairs(ParseContext p)
{
KVPair pair;
Dictionary<string, JSONValue> pairs = new Dictionary<string, JSONValue>();
while((pair = parse_kvpair(p)) != null)
{
pairs[pair.key] = pair.value;
parse_ws(p); // skip space if any
if (!parse_char(p, ','))
break;
}
return pairs;
}
private static KVPair parse_kvpair(ParseContext p)
{
int start = p.Tell();
KVPair pair = new KVPair();
parse_ws(p); // skip white space
string s = parse_string_token(p);
if (s == null)
return null;
parse_ws(p); // skip whitespace
if(!parse_char(p, ':'))
{
p.Seek(start);
return null;
}
JSONValue v = parse_jsonvalue(p);
if(v == null)
{
p.Seek(start);
return null;
}
return new KVPair()
{
key = s,
value = v
};
}
private static JSONValue parse_jsonvalue(ParseContext p)
{
JSONValue v;
parse_ws(p);
if (
(v = parse_json_null(p) ) != null ||
(v = parse_json_true(p) ) != null ||
(v = parse_json_false(p) ) != null ||
(v = parse_json_number(p)) != null ||
(v = parse_json_string(p)) != null ||
(v = parse_json_array(p) ) != null ||
(v = parse_json_object(p)) != null
)
{
}
parse_ws(p);
return v;
}
public static JSONValue parse(string s)
{
return parse(s.ToCharArray());
}
public static JSONValue parse(char[] s)
{
ParseContext p = new ParseContext(s);
JSONValue v = parse_jsonvalue(p);
if (p.isEOT())
return v;
else
return null;
}
public static string stringify()
{
return "null";
}
public static string stringify(bool b)
{
if (b)
return "true";
else
return "false";
}
private static bool shouldescape(char c)
{
return !(c >= ' ' && c != '"' && c != '\\');
}
private static string escape(string s)
{
using (var ms = new MemoryStream())
using (var wr = new StreamWriter(ms))
{
foreach(char c in s)
{
if(shouldescape(c))
{
int x = c;
wr.Write("\\u");
wr.Write(x.ToString("X4"));
} else
{
wr.Write(c);
}
}
wr.Flush();
ms.Seek(0, SeekOrigin.Begin);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
public static string stringify(string s)
{
return String.Format("\"{0}\"",escape(s));
}
public static string stringify(double d)
{
return d.ToString();
}
public static string stringify(List<object> l)
{
List<string> parts = new List<string>();
foreach(var o in l)
{
if (o == null)
{
parts.Add("null");
continue;
}
Type t = o.GetType();
do
{
if(t == typeof(bool))
{
if ((bool)o)
parts.Add("true");
else
parts.Add("false");
break;
}
if (t == typeof(string))
{
parts.Add(stringify((string)o));
break;
}
if (t == typeof(int) || t == typeof(double) || t == typeof(float))
{
parts.Add(stringify(Convert.ToDouble(o)));
break;
}
if (t == typeof(List<object>))
{
parts.Add(stringify((List<object>)o));
break;
}
if (t == typeof(Dictionary<string, object>))
{
parts.Add(stringify((Dictionary<string, object>)o));
break;
}
throw new InvalidDataException();
} while (false);
}
return "[" + String.Join(",", parts.ToArray()) + "]";
}
public static string stringify(Dictionary<string, object> o)
{
List<string> parts = new List<string>();
foreach(KeyValuePair<string, object> kv in o)
{
if (kv.Value == null)
{
parts.Add(String.Format("{0}:null", stringify(kv.Key)));
continue;
}
Type t = kv.Value.GetType();
do
{
if (t == typeof(bool))
{
if ((bool)kv.Value)
parts.Add(String.Format("{0}:true", stringify(kv.Key)));
else
parts.Add(String.Format("{0}:false", stringify(kv.Key)));
break;
}
if (t == typeof(string))
{
parts.Add(String.Format("{0}:{1}", stringify(kv.Key), stringify((string)kv.Value)));
break;
}
if (t == typeof(int) || t == typeof(double) || t == typeof(float))
{
parts.Add(String.Format("{0}:{1}", stringify(kv.Key), stringify(Convert.ToDouble(kv.Value))));
break;
}
if (t == typeof(List<object>))
{
parts.Add(String.Format("{0}:{1}", stringify(kv.Key), stringify((List<object>)kv.Value)));
break;
}
if (t == typeof(Dictionary<string, object>))
{
parts.Add(String.Format("{0}:{1}", stringify(kv.Key), stringify((Dictionary<string, object>)kv.Value)));
break;
}
throw new InvalidDataException();
} while (false);
}
return "{" + String.Join(",", parts.ToArray()) + "}";
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment