2 using System.Collections;
\r
3 using System.Collections.Generic;
\r
4 using System.Dynamic;
\r
6 using System.Reflection;
\r
8 using System.Text.RegularExpressions;
\r
10 namespace KancolleSniffer
\r
12 public class JsonParser
\r
14 private readonly string _source;
\r
15 private int _position;
\r
17 public static JsonObject Parse(string json)
\r
19 return new JsonParser(json).Parse();
\r
22 private JsonParser(string json)
\r
27 private JsonObject Parse()
\r
29 var ch = NextChar();
\r
30 if ('0' <= ch && ch <= '9' || ch == '-')
\r
31 return ParseNumber();
\r
35 if (IsMatch("null"))
\r
39 if (IsMatch("true"))
\r
40 return new JsonObject(true);
\r
43 if (IsMatch("false"))
\r
44 return new JsonObject(false);
\r
47 return ParseString();
\r
49 return ParseArray();
\r
51 return ParseObject();
\r
54 throw new JsonParserException($"'{ch}'不正な文字です。 at {_position}");
\r
57 private bool IsMatch(string s)
\r
59 foreach (char ch in s)
\r
61 var src = LookAhead();
\r
69 private JsonObject ParseNumber()
\r
71 return new JsonObject(GetNumber());
\r
74 private double GetNumber()
\r
77 var ch = LookAhead();
\r
85 while ('0' <= ch && ch <= '9')
\r
87 result = (result * 10.0) + (ch - '0');
\r
92 return sign * result;
\r
96 while ('0' <= ch && ch <= '9')
\r
98 result += (ch - '0') * exp;
\r
103 return sign * result;
\r
106 private JsonObject ParseString()
\r
108 return new JsonObject(GetString());
\r
111 private string GetString()
\r
117 var ch = LookAhead();
\r
133 return Unescape(_source.Substring(_position - len - 1, len));
\r
136 private static readonly Regex EscapeRegex =
\r
137 new Regex(@"\\[^u]|\\u(?:[0-9A-Fa-f]{4})", RegexOptions.Compiled);
\r
139 private string Unescape(string s)
\r
141 return EscapeRegex.Replace(s, m =>
\r
143 switch (m.Value[1])
\r
146 return '/'.ToString();
\r
155 return Regex.Unescape(m.Value).ToString();
\r
157 throw new JsonParserException("不正なエスケープシーケンスです。 at {");
\r
162 private JsonObject ParseArray()
\r
165 var ary = new List<JsonObject>();
\r
168 var ch = NextChar();
\r
172 return new JsonObject(ary);
\r
176 if (ch != ',' && ch != ']')
\r
177 throw new JsonParserException($"','か']'が必要です。 at {_position}");
\r
181 return new JsonObject(ary);
\r
187 private JsonObject ParseObject()
\r
190 var dict = new Dictionary<string, JsonObject>();
\r
193 var ch = NextChar();
\r
197 return new JsonObject(dict);
\r
200 throw new JsonParserException($"文字列が必要です。 at {_position}");
\r
201 var key = GetString();
\r
204 throw new JsonParserException($"':'が必要です。 at {_position}");
\r
206 var value = Parse();
\r
207 dict.Add(key, value);
\r
209 if (ch != ',' && ch != '}')
\r
210 throw new JsonParserException($"','か'}}'が必要です。 at {_position}");
\r
214 return new JsonObject(dict);
\r
220 private char LookAhead()
\r
222 if (_source.Length == _position)
\r
224 return _source[_position];
\r
227 private void Consume()
\r
229 if (_source.Length == _position)
\r
230 throw new JsonParserException($"入力が途切れています。 at {_position}");
\r
234 private char NextChar()
\r
238 var ch = LookAhead();
\r
239 if (!(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' '))
\r
246 public class JsonObject : DynamicObject
\r
248 private readonly JsonType _type;
\r
249 private readonly bool _bool;
\r
250 private readonly double _number;
\r
251 private readonly string _string;
\r
252 private readonly List<JsonObject> _array;
\r
253 private readonly Dictionary<string, JsonObject> _dict;
\r
255 public bool IsArray => _type == JsonType.Array;
\r
256 public bool IsObject => _type == JsonType.Object;
\r
257 public bool IsDefined(string attr) => IsObject && _dict.ContainsKey(attr);
\r
259 public JsonObject(bool b)
\r
261 _type = JsonType.Bool;
\r
265 public JsonObject(double n)
\r
267 _type = JsonType.Number;
\r
271 public JsonObject(string s)
\r
273 _type = JsonType.String;
\r
277 public JsonObject(List<JsonObject> ary)
\r
279 _type = JsonType.Array;
\r
283 public JsonObject(Dictionary<string, JsonObject> dict)
\r
285 _type = JsonType.Object;
\r
289 public override bool TryGetMember(GetMemberBinder binder, out object result)
\r
292 if (_type != JsonType.Object)
\r
294 if (!_dict.TryGetValue(binder.Name, out var dict))
\r
296 result = dict?.Value;
\r
300 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
\r
302 result = _type == JsonType.Object && _dict.ContainsKey(binder.Name);
\r
306 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
\r
310 case JsonType.Array:
\r
311 result = _array[(int)indexes[0]]?.Value;
\r
313 case JsonType.Object:
\r
314 result = _dict[(string)indexes[0]]?.Value;
\r
321 public override bool TryConvert(ConvertBinder binder, out object result)
\r
323 if (binder.Type == typeof(IEnumerable))
\r
327 case JsonType.Array:
\r
328 result = _array.Select(x => x.Value);
\r
330 case JsonType.Object:
\r
331 result = _dict.Select(x => new KeyValuePair<string, dynamic>(x.Key, x.Value));
\r
338 if (_type != JsonType.Array)
\r
339 return ConvertPrivateType(binder.Type, out result);
\r
340 if (binder.Type == typeof(int[]))
\r
342 result = _array.Select(x => (int)x._number).ToArray();
\r
345 if (binder.Type.IsArray)
\r
347 return ConvertArray(binder.Type.GetElementType(), _array, out result);
\r
353 private bool ConvertPrivateType(Type type, out object result)
\r
355 if (type == typeof(bool) && _type == JsonType.Bool)
\r
360 if (type == typeof(int) && _type == JsonType.Number)
\r
362 result = (int)_number;
\r
365 if (type == typeof(double) && _type == JsonType.Number)
\r
370 if (type == typeof(string) && _type == JsonType.String)
\r
375 if (type == typeof(object))
\r
384 private bool ConvertArray(Type type, List<JsonObject> values, out object result)
\r
387 var array = Array.CreateInstance(type, values.Count);
\r
388 for (var i = 0; i < array.Length; i++)
\r
392 if (!values[i].IsArray || !ConvertArray(type.GetElementType(), values[i]._array, out var one))
\r
394 array.SetValue((dynamic)one, i);
\r
398 if (!values[i].ConvertPrivateType(type, out var one))
\r
400 array.SetValue((dynamic)one, i);
\r
407 public static JsonObject CreateJsonObject(object value)
\r
412 return new JsonObject(s);
\r
414 return new JsonObject(i);
\r
416 return new JsonObject(d);
\r
417 case JsonObject json:
\r
419 case IEnumerable arry:
\r
420 return new JsonObject(arry.Cast<object>().Select(CreateJsonObject).ToList());
\r
422 return new JsonObject(obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
\r
423 .ToDictionary(prop => prop.Name, prop => CreateJsonObject(prop.GetValue(obj))));
\r
428 public override bool TrySetMember(SetMemberBinder binder, object value)
\r
430 if (_type != JsonType.Object)
\r
432 _dict[binder.Name] = CreateJsonObject(value);
\r
436 public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
\r
438 if (_type == JsonType.Array)
\r
440 _array[(int)indexes[0]] = CreateJsonObject(value);
\r
443 if (_type == JsonType.Object)
\r
445 _dict[(string)indexes[0]] = CreateJsonObject(value);
\r
451 public override string ToString()
\r
453 var sb = new StringBuilder();
\r
454 ConvertToString(sb);
\r
455 return sb.ToString();
\r
458 private void ConvertToString(StringBuilder sb)
\r
462 case JsonType.Bool:
\r
463 sb.Append(_bool ? "true" : "false");
\r
465 case JsonType.Number:
\r
466 sb.Append(_number.ToString("G"));
\r
468 case JsonType.String:
\r
470 sb.Append(HttpUtility.JavascriptStringEncode(_string));
\r
473 case JsonType.Array:
\r
475 var delimiter = "";
\r
476 foreach (var val in _array)
\r
478 sb.Append(delimiter);
\r
485 val.ConvertToString(sb);
\r
491 case JsonType.Object:
\r
494 foreach (var entry in _dict)
\r
496 sb.Append(delimiter);
\r
498 sb.Append(entry.Key);
\r
500 if (entry.Value == null)
\r
506 entry.Value.ConvertToString(sb);
\r
515 private object Value
\r
521 case JsonType.Bool:
\r
523 case JsonType.Number:
\r
525 case JsonType.String:
\r
532 private enum JsonType
\r
542 public class JsonParserException : Exception
\r
544 public JsonParserException()
\r
548 public JsonParserException(string message) : base(message)
\r