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 }; } } }