using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace ServiceSim
{
///
/// 小さな JSON の読み取り。サイトが書き出した店の形の JSON を読むためだけに使う。
/// Unity の JsonUtility は入れ子の配列([[x, y], [x, y]])や、キーの決まっていない
/// 入れ物を読めないので、ここに最小限のものを置く。書き出しはしない。
///
/// 読めるもの: オブジェクト / 配列 / 文字列 / 数 / true / false / null
/// 読めないもの: コメント、末尾のカンマ(JSON の決まりどおりのものだけ読む)
///
public class JsonValue
{
public enum Kind { Null, Bool, Number, String, Array, Object }
public Kind kind = Kind.Null;
public bool boolValue;
public double number;
public string text;
public List items;
public Dictionary members;
static readonly JsonValue NullValue = new JsonValue();
public bool IsNull { get { return kind == Kind.Null; } }
public bool IsArray { get { return kind == Kind.Array; } }
public bool IsObject { get { return kind == Kind.Object; } }
/// 配列の長さ(配列でなければ 0)
public int Count { get { return kind == Kind.Array && items != null ? items.Count : 0; } }
/// 配列の i 番目。無ければ null 値(例外を投げない)
public JsonValue this[int i]
{
get
{
if (kind != Kind.Array || items == null || i < 0 || i >= items.Count) return NullValue;
return items[i];
}
}
/// オブジェクトの key。無ければ null 値(例外を投げない)
public JsonValue this[string key]
{
get
{
JsonValue v;
if (kind == Kind.Object && members != null && members.TryGetValue(key, out v)) return v;
return NullValue;
}
}
public bool Has(string key)
{
return kind == Kind.Object && members != null && members.ContainsKey(key);
}
public IEnumerable Keys
{
get
{
if (kind == Kind.Object && members != null)
foreach (string k in members.Keys) yield return k;
}
}
public float F(float fallback = 0f)
{
return kind == Kind.Number ? (float)number : fallback;
}
public int I(int fallback = 0)
{
return kind == Kind.Number ? (int)Math.Round(number) : fallback;
}
public string S(string fallback = null)
{
return kind == Kind.String ? text : fallback;
}
public bool B(bool fallback = false)
{
if (kind == Kind.Bool) return boolValue;
if (kind == Kind.Number) return number != 0.0;
return fallback;
}
// ---- 読み取り ----
/// 読む。読めなければ error に理由を入れて null を返す
public static JsonValue Parse(string src, out string error)
{
error = null;
if (string.IsNullOrEmpty(src)) { error = "中身が空です"; return null; }
int i = 0;
try
{
JsonValue v = ParseValue(src, ref i);
SkipWhite(src, ref i);
if (i < src.Length)
{
error = "終わりのあとに文字が残っています(位置 " + i + ")";
return null;
}
return v;
}
catch (Exception e)
{
error = e.Message;
return null;
}
}
static void SkipWhite(string s, ref int i)
{
while (i < s.Length)
{
char c = s[i];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') i++;
else break;
}
}
static JsonValue ParseValue(string s, ref int i)
{
SkipWhite(s, ref i);
if (i >= s.Length) throw new Exception("途中で終わっています");
char c = s[i];
switch (c)
{
case '{': return ParseObject(s, ref i);
case '[': return ParseArray(s, ref i);
case '"': return new JsonValue { kind = Kind.String, text = ParseString(s, ref i) };
case 't': Expect(s, ref i, "true"); return new JsonValue { kind = Kind.Bool, boolValue = true };
case 'f': Expect(s, ref i, "false"); return new JsonValue { kind = Kind.Bool, boolValue = false };
case 'n': Expect(s, ref i, "null"); return new JsonValue();
default: return ParseNumber(s, ref i);
}
}
static void Expect(string s, ref int i, string word)
{
if (i + word.Length > s.Length || string.CompareOrdinal(s, i, word, 0, word.Length) != 0)
throw new Exception("読めない語があります(位置 " + i + ")");
i += word.Length;
}
static JsonValue ParseObject(string s, ref int i)
{
JsonValue v = new JsonValue { kind = Kind.Object, members = new Dictionary() };
i++; // '{'
SkipWhite(s, ref i);
if (i < s.Length && s[i] == '}') { i++; return v; }
while (true)
{
SkipWhite(s, ref i);
if (i >= s.Length || s[i] != '"') throw new Exception("キーが文字列ではありません(位置 " + i + ")");
string key = ParseString(s, ref i);
SkipWhite(s, ref i);
if (i >= s.Length || s[i] != ':') throw new Exception("コロンがありません(位置 " + i + ")");
i++;
v.members[key] = ParseValue(s, ref i);
SkipWhite(s, ref i);
if (i >= s.Length) throw new Exception("オブジェクトが閉じていません");
if (s[i] == ',') { i++; continue; }
if (s[i] == '}') { i++; return v; }
throw new Exception("オブジェクトの区切りが読めません(位置 " + i + ")");
}
}
static JsonValue ParseArray(string s, ref int i)
{
JsonValue v = new JsonValue { kind = Kind.Array, items = new List() };
i++; // '['
SkipWhite(s, ref i);
if (i < s.Length && s[i] == ']') { i++; return v; }
while (true)
{
v.items.Add(ParseValue(s, ref i));
SkipWhite(s, ref i);
if (i >= s.Length) throw new Exception("配列が閉じていません");
if (s[i] == ',') { i++; continue; }
if (s[i] == ']') { i++; return v; }
throw new Exception("配列の区切りが読めません(位置 " + i + ")");
}
}
static string ParseString(string s, ref int i)
{
i++; // '"'
StringBuilder sb = new StringBuilder();
while (i < s.Length)
{
char c = s[i++];
if (c == '"') return sb.ToString();
if (c != '\\') { sb.Append(c); continue; }
if (i >= s.Length) break;
char e = s[i++];
switch (e)
{
case '"': sb.Append('"'); break;
case '\\': sb.Append('\\'); break;
case '/': sb.Append('/'); break;
case 'b': sb.Append('\b'); break;
case 'f': sb.Append('\f'); break;
case 'n': sb.Append('\n'); break;
case 'r': sb.Append('\r'); break;
case 't': sb.Append('\t'); break;
case 'u':
if (i + 4 > s.Length) throw new Exception("\\u が短すぎます(位置 " + i + ")");
sb.Append((char)Convert.ToInt32(s.Substring(i, 4), 16));
i += 4;
break;
default: throw new Exception("読めない逃げ文字 \\" + e + "(位置 " + i + ")");
}
}
throw new Exception("文字列が閉じていません");
}
static JsonValue ParseNumber(string s, ref int i)
{
int start = i;
if (i < s.Length && (s[i] == '-' || s[i] == '+')) i++;
while (i < s.Length)
{
char c = s[i];
if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') i++;
else break;
}
string t = s.Substring(start, i - start);
double d;
if (!double.TryParse(t, NumberStyles.Float, CultureInfo.InvariantCulture, out d))
throw new Exception("数として読めません: " + t + "(位置 " + start + ")");
return new JsonValue { kind = Kind.Number, number = d };
}
}
}