2 using System.Collections;
\r
3 using System.Collections.Generic;
\r
4 using System.Dynamic;
\r
6 using System.Text.RegularExpressions;
\r
8 namespace KancolleSniffer
\r
10 public class JsonParser
\r
12 private readonly string _source;
\r
13 private int _position;
\r
15 public static JsonObject Parse(string json)
\r
17 return new JsonParser(json).Parse();
\r
20 private JsonParser(string json)
\r
25 private JsonObject Parse()
\r
27 var ch = NextChar();
\r
28 if ('0' <= ch && ch <= '9' || ch == '-')
\r
29 return ParseNumber();
\r
33 if (IsMatch("null"))
\r
37 if (IsMatch("true"))
\r
38 return new JsonObject(true);
\r
41 if (IsMatch("false"))
\r
42 return new JsonObject(false);
\r
45 return ParseString();
\r
47 return ParseArray();
\r
49 return ParseObject();
\r
52 throw new JsonParserException($"'{ch}'不正な文字です。 at {_position}");
\r
55 private bool IsMatch(string s)
\r
57 foreach (char ch in s)
\r
59 var src = LookAhead();
\r
67 private JsonObject ParseNumber()
\r
69 return new JsonObject(GetNumber());
\r
72 private double GetNumber()
\r
75 var ch = LookAhead();
\r
83 while ('0' <= ch && ch <= '9')
\r
85 result = (result * 10.0) + (ch - '0');
\r
90 return sign * result;
\r
94 while ('0' <= ch && ch <= '9')
\r
96 result += (ch - '0') * exp;
\r
101 return sign * result;
\r
104 private JsonObject ParseString()
\r
106 return new JsonObject(GetString());
\r
109 private string GetString()
\r
115 var ch = LookAhead();
\r
131 return Unescape(_source.Substring(_position - len - 1, len));
\r
134 private static readonly Regex EscapeRegex =
\r
135 new Regex(@"\\[^u]|\\u(?:[0-9A-Fa-f]{4})", RegexOptions.Compiled);
\r
137 private string Unescape(string s)
\r
139 return EscapeRegex.Replace(s, m =>
\r
141 switch (m.Value[1])
\r
144 return '/'.ToString();
\r
153 return Regex.Unescape(m.Value).ToString();
\r
155 throw new JsonParserException("不正なエスケープシーケンスです。 at {");
\r
160 private JsonObject ParseArray()
\r
163 var ary = new List<JsonObject>();
\r
166 var ch = NextChar();
\r
170 return new JsonObject(ary);
\r
174 if (ch != ',' && ch != ']')
\r
175 throw new JsonParserException($"','か']'が必要です。 at {_position}");
\r
179 return new JsonObject(ary);
\r
185 private JsonObject ParseObject()
\r
188 var dict = new Dictionary<string, JsonObject>();
\r
191 var ch = NextChar();
\r
195 return new JsonObject(dict);
\r
198 throw new JsonParserException($"文字列が必要です。 at {_position}");
\r
199 var key = GetString();
\r
202 throw new JsonParserException($"':'が必要です。 at {_position}");
\r
204 var value = Parse();
\r
205 dict.Add(key, value);
\r
207 if (ch != ',' && ch != '}')
\r
208 throw new JsonParserException($"','か'}}'が必要です。 at {_position}");
\r
212 return new JsonObject(dict);
\r
218 private char LookAhead()
\r
220 if (_source.Length == _position)
\r
222 return _source[_position];
\r
225 private void Consume()
\r
227 if (_source.Length == _position)
\r
228 throw new JsonParserException($"入力が途切れています。 at {_position}");
\r
232 private char NextChar()
\r
236 var ch = LookAhead();
\r
237 if (!(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' '))
\r
244 public class JsonObject : DynamicObject
\r
246 private readonly JsonType _type;
\r
247 private readonly bool _bool;
\r
248 private readonly double _number;
\r
249 private readonly string _string;
\r
250 private readonly List<JsonObject> _array;
\r
251 private readonly Dictionary<string, JsonObject> _dict;
\r
253 public bool IsArray => _type == JsonType.Array;
\r
254 public bool IsObject => _type == JsonType.Object;
\r
255 public bool IsDefined(string attr) => IsObject && _dict.ContainsKey(attr);
\r
257 public JsonObject(bool b)
\r
259 _type = JsonType.Bool;
\r
263 public JsonObject(double n)
\r
265 _type = JsonType.Number;
\r
269 public JsonObject(string s)
\r
271 _type = JsonType.String;
\r
275 public JsonObject(List<JsonObject> ary)
\r
277 _type = JsonType.Array;
\r
281 public JsonObject(Dictionary<string, JsonObject> dict)
\r
283 _type = JsonType.Object;
\r
287 public override bool TryGetMember(GetMemberBinder binder, out object result)
\r
290 if (_type != JsonType.Object)
\r
292 if (!_dict.TryGetValue(binder.Name, out var dict))
\r
294 result = dict?.Value;
\r
298 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
\r
300 result = _type == JsonType.Object && _dict.ContainsKey(binder.Name);
\r
304 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
\r
308 case JsonType.Array:
\r
309 result = _array[(int)indexes[0]]?.Value;
\r
311 case JsonType.Object:
\r
312 result = _dict[(string)indexes[0]]?.Value;
\r
319 public override bool TryConvert(ConvertBinder binder, out object result)
\r
321 if (binder.Type == typeof(IEnumerable))
\r
325 case JsonType.Array:
\r
326 result = _array.Select(x => x.Value);
\r
328 case JsonType.Object:
\r
329 result = _dict.Select(x => new KeyValuePair<string, dynamic>(x.Key, x.Value));
\r
336 if (_type != JsonType.Array)
\r
337 return ConvertPrivateType(binder.Type, out result);
\r
338 if (binder.Type == typeof(int[]))
\r
340 result = _array.Select(x => (int)x._number).ToArray();
\r
343 if (binder.Type.IsArray)
\r
345 return ConvertArray(binder.Type.GetElementType(), _array, out result);
\r
351 private bool ConvertPrivateType(Type type, out object result)
\r
353 if (type == typeof(bool) && _type == JsonType.Bool)
\r
358 if (type == typeof(int) && _type == JsonType.Number)
\r
360 result = (int)_number;
\r
363 if (type == typeof(double) && _type == JsonType.Number)
\r
368 if (type == typeof(string) && _type == JsonType.String)
\r
373 if (type == typeof(object))
\r
382 private bool ConvertArray(Type type, List<JsonObject> values, out object result)
\r
385 var array = Array.CreateInstance(type, values.Count);
\r
386 for (var i = 0; i < array.Length; i++)
\r
390 if (!values[i].IsArray || !ConvertArray(type.GetElementType(), values[i]._array, out var one))
\r
392 array.SetValue((dynamic)one, i);
\r
396 if (!values[i].ConvertPrivateType(type, out var one))
\r
398 array.SetValue((dynamic)one, i);
\r
405 private object Value
\r
411 case JsonType.Bool:
\r
413 case JsonType.Number:
\r
415 case JsonType.String:
\r
422 private enum JsonType
\r
432 public class JsonParserException : Exception
\r
434 public JsonParserException()
\r
438 public JsonParserException(string message) : base(message)
\r