Skip to content

Instantly share code, notes, and snippets.

@quartorz
Last active August 29, 2015 14:06
Show Gist options
  • Save quartorz/a27c7b4d142b02c65dd7 to your computer and use it in GitHub Desktop.
Save quartorz/a27c7b4d142b02c65dd7 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Diagnostics;
namespace Json
{
class Object : Dictionary<string, Parser.JsonItem> { }
class Array : List<Parser.JsonItem> { }
/// <summary>
/// JSONパーサ
/// <see cref="Parse"/>静的メソッドでパースを実行する
/// </summary>
/// <example>
/// 文字列をパースして値を取り出す例
/// <code>
/// var y = Json.Parser.Parse(@"{""key0"" : [ ""val0"", ""val1""] , ""key1"" : ""string"", ""key2"" : false , ""key3"" : -20.33e-1 , ""key4"" : null, ""key5"" : {} }");
///
/// foreach (var v in y.Object()["key0"].Array())
/// {
/// Console.WriteLine(v.String());
/// }
///
/// Console.WriteLine(y.Object()["key1"].String());
/// Console.WriteLine(y.Object()["key2"].Bool());
/// Console.WriteLine(y.Object()["key3"].Number());
/// Console.WriteLine(y.Object()["key3"].IsNull());
/// Console.WriteLine(y.Object()["key4"].IsNull());
/// Console.WriteLine(y.Object()["key5"].Object().Count);
/// </code>
/// 出力
/// val0
/// val1
/// key2
/// False
/// -2.033
/// False
/// True
/// 0
/// </example>
class Parser
{
/// <summary>
/// パースした結果を保持する
/// <see cref="ToString"/>メソッドで文字列にシリアライズする
/// </summary>
/// <example>
/// 文字列をパースして得られたオブジェクトを書き換えて再び文字列に変換する例
/// <code>
/// var y = Json.Parser.Parse(@"{""key0"" : [ ""val0"", ""val1""] , ""key1"" : ""string"", ""key2"" : false , ""key3"" : -20.33e-1 , ""key4"" : null, ""key5"" : {} }");
///
/// y.Object()["key0"].Array()[0].SetNull();
/// y.Object()["key0"].Array()[1] = 100;
/// y.Object()["key1"] = new Json.Object();
/// y.Object()["key1"].Object()["newkey"] = "newvalue";
/// y.Object()["key2"] = new Json.Array();
/// y.Object()["key2"].Array().Add(true);
/// y.Object().Remove("key5");
///
/// Console.WriteLine(y.ToString());
/// </code>
/// 出力
/// {"key0":[null,100],"key1":{"newkey":"newvalue"},"key2":[true],"key3":-2.033,"key4":null}
/// </example>
public class JsonItem
{
private enum Type
{
Null,
Bool,
Number,
String,
Object,
Array,
}
private Type type;
private bool boolean;
private double number;
private object item;
public bool IsNull()
{
return type == Type.Null;
}
public bool Bool()
{
Debug.Assert(type == Type.Bool);
return boolean;
}
public double Number()
{
Debug.Assert(type == Type.Number);
return number;
}
public string String()
{
Debug.Assert(type == Type.String);
return item as string;
}
public object Null()
{
Debug.Assert(type == Type.Null);
return null;
}
public Dictionary<string, JsonItem> Object()
{
Debug.Assert(type == Type.Object);
return item as Dictionary<string, JsonItem>;
}
public List<JsonItem> Array()
{
Debug.Assert(type == Type.Array);
return item as List<JsonItem>;
}
public void SetNull()
{
type = Type.Null;
}
public static implicit operator JsonItem(bool b)
{
var item = new JsonItem();
item.type = Type.Bool;
item.boolean = b;
return item;
}
public static implicit operator JsonItem(double num)
{
var item = new JsonItem();
item.type = Type.Number;
item.number = num;
return item;
}
public static implicit operator JsonItem(string str)
{
var item = new JsonItem();
item.type = Type.String;
item.item = str;
return item;
}
public static implicit operator JsonItem(Object obj)
{
var item = new JsonItem();
item.type = Type.Object;
item.item = obj;
return item;
}
public static implicit operator JsonItem(Array arr)
{
var item = new JsonItem();
item.type = Type.Array;
item.item = arr;
return item;
}
public override string ToString()
{
switch (type)
{
case Type.Null:
return "null";
case Type.Bool:
return boolean ? "true" : "false";
case Type.Number:
return number.ToString();
case Type.String:
return EscapeString(item as string);
case Type.Object:
var r = "{";
foreach (var kv in item as Object)
{
r += EscapeString(kv.Key);
r += ':';
r += kv.Value.ToString();
r += ',';
}
if (r[r.Length - 1] == ',')
{
r = r.Remove(r.Length - 1);
}
r += '}';
return r;
case Type.Array:
r = "[";
foreach (var v in item as Array)
{
r += v.ToString();
r += ',';
}
if (r[r.Length - 1] == ',')
{
r = r.Remove(r.Length - 1);
}
r += ']';
return r;
}
// unreachable
return null;
}
private static string EscapeString(string s)
{
var r = "\"";
foreach (char c in s)
{
switch (c)
{
case '"':
r += @"\""";
break;
case '\\':
r += @"\\";
break;
case '/':
r += @"\/";
break;
case '\b':
r += @"\b";
break;
case '\f':
r += @"\f";
break;
case '\n':
r += @"\n";
break;
case '\r':
r += @"\r";
break;
case '\t':
r += @"\t";
break;
default:
if (0x20 <= c && c <= 0x7e)
{
r += c;
}
else
{
r += @"\u";
r += Convert.ToString(c, 16);
}
break;
}
}
r += '"';
return r;
}
}
static private Regex hexChar = new Regex("[0-9a-fA-F]{4}");
static private Regex number = new Regex(@"\-?(0|[1-9][0-9]*)(\.[0-9]+)?((e|E)(\+|\-)?[0-9]+)?");
public static JsonItem Parse(string str)
{
return Parse(ref str);
}
public static JsonItem Parse(ref string str)
{
int index = 0;
var obj = ParseImpl(ref str, ref index);
SkipWhiteSpaces(ref str, ref index);
if (index != str.Length)
{
throw new Exception("");
}
return obj;
}
private static JsonItem ParseImpl(ref string str, ref int index)
{
var result = new JsonItem();
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input");
}
switch (str[index])
{
case '"':
result = ParseString(ref str, ref index);
break;
case '[':
index++;
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing array (expected ']' or '\"')");
}
var array = new Array();
if (str[index] != ']')
{
for (; ; )
{
array.Add(ParseImpl(ref str, ref index));
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing array (expected ']' or '\"')");
}
if (str[index] == ']')
{
break;
}
else if (str[index] == ',')
{
index++;
}
else throw new Exception(string.Format("invalid token in array (expected ']' or ',', got '{0}', position: {1})", str[index], index));
}
}
index++;
result = array;
break;
case '{':
index++;
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing object (expected '}' or '\"')");
}
var obj = new Object();
if (str[index] != '}')
{
for (; ; )
{
if (str[index] != '"')
{
throw new Exception(string.Format("invalid token in object (expected '\"', got '{0}', position: {1})", str[index], index));
}
var key = ParseString(ref str, ref index);
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing object (expected ':')");
}
if (str[index] != ':')
{
throw new Exception(string.Format("invalid token in object (expected ':', got '{0}', position: {1})", str[index], index));
}
index++;
var val = ParseImpl(ref str, ref index);
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing object (expected '}' or ',')");
}
obj.Add(key, val);
if (str[index] == '}')
{
break;
}
else if (str[index] == ',')
{
index++;
SkipWhiteSpaces(ref str, ref index);
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing object (expected '\"')");
}
}
else throw new Exception(string.Format("invalid token in object (expected '}' or ',', got '{0}', position: {1})", str[index], index));
}
}
index++;
result = obj;
break;
default:
if (str.Substring(index).StartsWith("true"))
{
result = true;
index += 4;
break;
}
else if (str.Substring(index).StartsWith("false"))
{
result = false;
index += 5;
break;
}
else if (str.Substring(index).StartsWith("null"))
{
result.SetNull();
index += 4;
break;
}
var match = number.Match(str, index);
if (!match.Success || match.Index != index)
{
throw new Exception(string.Format("unknown character '{0}' (position: {1})", str[index], index));
}
result = Convert.ToDouble(match.Value);
index += match.Length;
break;
}
return result;
}
private static void SkipWhiteSpaces(ref string str, ref int index)
{
while (index < str.Length && (str[index] == ' ' || str[index] == '\r' || str[index] == '\n' || str[index] == '\t'))
{
index++;
}
}
private static string ParseString(ref string str, ref int index)
{
var result = "";
char ch;
index++;
for (; ; )
{
ch = str[index++];
if (ch == '"')
{
return result;
}
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing string");
}
if (ch == '\\')
{
ch = str[index++];
if (index >= str.Length)
{
throw new Exception("reached to the end of input when parsing string");
}
switch (ch)
{
case '"':
result += '"';
break;
case '\\':
result += '\\';
break;
case '/':
result += '/';
break;
case 'b':
result += '\b';
break;
case 'f':
result += '\f';
break;
case 'n':
result += '\n';
break;
case 'r':
result += '\r';
break;
case 't':
result += '\t';
break;
case 'u':
var m = hexChar.Match(str, index, 4);
if (!m.Success)
{
throw new Exception(string.Format("invalid escape sequence in string (got \"\\u{0}\", position: {1})", str.Substring(index, 4), index));
}
result += Convert.ToChar(Convert.ToInt16(m.Value, 16));
break;
default:
throw new Exception(string.Format("unknown escape sequence \"\\{0}\" in string (position: {1})", str[index], index));
}
}
else
{
result += ch;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment