2 using System.Collections;
\r
3 using System.Collections.Generic;
\r
4 using System.Collections.Specialized;
\r
5 using System.Dynamic;
\r
7 using System.Reflection;
\r
9 using System.Text.RegularExpressions;
\r
11 namespace KancolleSniffer
\r
13 public class JsonParser
\r
15 private readonly string _source;
\r
16 private int _position;
\r
18 public static JsonObject Parse(string json)
\r
20 return new JsonParser(json).Parse();
\r
23 private JsonParser(string json)
\r
28 private JsonObject Parse()
\r
30 var ch = NextChar();
\r
31 if ('0' <= ch && ch <= '9' || ch == '-')
\r
32 return ParseNumber();
\r
36 if (IsMatch("null"))
\r
40 if (IsMatch("true"))
\r
41 return new JsonObject(true);
\r
44 if (IsMatch("false"))
\r
45 return new JsonObject(false);
\r
48 return ParseString();
\r
50 return ParseArray();
\r
52 return ParseObject();
\r
55 throw new JsonParserException($"'{ch}'不正な文字です。 at {_position}");
\r
58 private bool IsMatch(string s)
\r
60 foreach (char ch in s)
\r
62 var src = LookAhead();
\r
70 private JsonObject ParseNumber()
\r
72 return new JsonObject(GetNumber());
\r
75 private double GetNumber()
\r
78 var ch = LookAhead();
\r
86 while ('0' <= ch && ch <= '9')
\r
88 result = (result * 10.0) + (ch - '0');
\r
93 return sign * result;
\r
97 while ('0' <= ch && ch <= '9')
\r
99 result += (ch - '0') * exp;
\r
104 return sign * result;
\r
107 private JsonObject ParseString()
\r
109 return new JsonObject(GetString());
\r
112 private string GetString()
\r
118 var ch = LookAhead();
\r
134 return Unescape(_source.Substring(_position - len - 1, len));
\r
137 private static readonly Regex EscapeRegex =
\r
138 new Regex(@"\\[^u]|\\u(?:[0-9A-Fa-f]{4})", RegexOptions.Compiled);
\r
140 private string Unescape(string s)
\r
142 return EscapeRegex.Replace(s, m =>
\r
144 switch (m.Value[1])
\r
147 return '/'.ToString();
\r
156 return Regex.Unescape(m.Value).ToString();
\r
158 throw new JsonParserException("不正なエスケープシーケンスです。 at {");
\r
163 private JsonObject ParseArray()
\r
166 var ary = new List<JsonObject>();
\r
169 var ch = NextChar();
\r
173 return new JsonObject(ary);
\r
177 if (ch != ',' && ch != ']')
\r
178 throw new JsonParserException($"','か']'が必要です。 at {_position}");
\r
182 return new JsonObject(ary);
\r
188 private JsonObject ParseObject()
\r
191 var dict = new OrderedDictionary();
\r
194 var ch = NextChar();
\r
198 return new JsonObject(dict);
\r
201 throw new JsonParserException($"文字列が必要です。 at {_position}");
\r
202 var key = GetString();
\r
205 throw new JsonParserException($"':'が必要です。 at {_position}");
\r
207 var value = Parse();
\r
208 dict.Add(key, value);
\r
210 if (ch != ',' && ch != '}')
\r
211 throw new JsonParserException($"','か'}}'が必要です。 at {_position}");
\r
215 return new JsonObject(dict);
\r
221 private char LookAhead()
\r
223 if (_source.Length == _position)
\r
225 return _source[_position];
\r
228 private void Consume()
\r
230 if (_source.Length == _position)
\r
231 throw new JsonParserException($"入力が途切れています。 at {_position}");
\r
235 private char NextChar()
\r
239 var ch = LookAhead();
\r
240 if (!(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' '))
\r
247 public class JsonObject : DynamicObject
\r
249 private readonly JsonType _type;
\r
250 private readonly bool _bool;
\r
251 private readonly double _number;
\r
252 private readonly string _string;
\r
253 private readonly List<JsonObject> _array;
\r
254 private readonly OrderedDictionary _dict;
\r
256 public bool IsArray => _type == JsonType.Array;
\r
257 public bool IsObject => _type == JsonType.Object;
\r
258 public bool IsDefined(string attr) => IsObject && _dict.Contains(attr);
\r
260 public JsonObject(bool b)
\r
262 _type = JsonType.Bool;
\r
266 public JsonObject(double n)
\r
268 _type = JsonType.Number;
\r
272 public JsonObject(string s)
\r
274 _type = JsonType.String;
\r
278 public JsonObject(List<JsonObject> ary)
\r
280 _type = JsonType.Array;
\r
284 public JsonObject(OrderedDictionary dict)
\r
286 _type = JsonType.Object;
\r
290 public override bool TryGetMember(GetMemberBinder binder, out object result)
\r
293 if (_type != JsonType.Object)
\r
295 if (!_dict.Contains(binder.Name))
\r
297 result = ((JsonObject)_dict[binder.Name])?.Value;
\r
301 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
\r
303 result = _type == JsonType.Object && _dict.Contains(binder.Name);
\r
307 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
\r
311 case JsonType.Array:
\r
312 result = _array[(int)indexes[0]]?.Value;
\r
314 case JsonType.Object:
\r
315 result = ((JsonObject)_dict[(string)indexes[0]])?.Value;
\r
322 public override bool TryConvert(ConvertBinder binder, out object result)
\r
324 if (binder.Type == typeof(IEnumerable))
\r
328 case JsonType.Array:
\r
329 result = _array.Select(x => x.Value);
\r
331 case JsonType.Object:
\r
332 result = _dict.Cast<DictionaryEntry>().Select(x => new KeyValuePair<string, dynamic>((string)x.Key, x.Value));
\r
339 if (_type != JsonType.Array)
\r
340 return ConvertPrivateType(binder.Type, out result);
\r
341 if (binder.Type == typeof(int[]))
\r
343 result = _array.Select(x => (int)x._number).ToArray();
\r
346 if (binder.Type.IsArray)
\r
348 return ConvertArray(binder.Type.GetElementType(), _array, out result);
\r
354 private bool ConvertPrivateType(Type type, out object result)
\r
356 if (type == typeof(bool) && _type == JsonType.Bool)
\r
361 if (type == typeof(int) && _type == JsonType.Number)
\r
363 result = (int)_number;
\r
366 if (type == typeof(double) && _type == JsonType.Number)
\r
371 if (type == typeof(string) && _type == JsonType.String)
\r
376 if (type == typeof(object))
\r
385 private bool ConvertArray(Type type, List<JsonObject> values, out object result)
\r
388 var array = Array.CreateInstance(type, values.Count);
\r
389 for (var i = 0; i < array.Length; i++)
\r
393 if (!values[i].IsArray || !ConvertArray(type.GetElementType(), values[i]._array, out var one))
\r
395 array.SetValue((dynamic)one, i);
\r
399 if (!values[i].ConvertPrivateType(type, out var one))
\r
401 array.SetValue((dynamic)one, i);
\r
408 public static JsonObject CreateJsonObject(object value)
\r
413 return new JsonObject(s);
\r
415 return new JsonObject(i);
\r
417 return new JsonObject(d);
\r
418 case JsonObject json:
\r
420 case IEnumerable arry:
\r
421 return new JsonObject(arry.Cast<object>().Select(CreateJsonObject).ToList());
\r
423 var dict = new OrderedDictionary();
\r
424 foreach (var prop in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
\r
425 dict.Add(prop.Name, CreateJsonObject(prop.GetValue(obj)));
\r
426 return new JsonObject(dict);
\r
431 public override bool TrySetMember(SetMemberBinder binder, object value)
\r
433 if (_type != JsonType.Object)
\r
435 _dict[binder.Name] = CreateJsonObject(value);
\r
439 public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
\r
441 if (_type == JsonType.Array)
\r
443 _array[(int)indexes[0]] = CreateJsonObject(value);
\r
446 if (_type == JsonType.Object)
\r
448 _dict[(string)indexes[0]] = CreateJsonObject(value);
\r
454 public override string ToString()
\r
456 var sb = new StringBuilder();
\r
457 ConvertToString(sb);
\r
458 return sb.ToString();
\r
461 private void ConvertToString(StringBuilder sb)
\r
465 case JsonType.Bool:
\r
466 sb.Append(_bool ? "true" : "false");
\r
468 case JsonType.Number:
\r
469 sb.Append(_number.ToString("G"));
\r
471 case JsonType.String:
\r
473 sb.Append(HttpUtility.JavascriptStringEncode(_string));
\r
476 case JsonType.Array:
\r
478 var delimiter = "";
\r
479 foreach (var val in _array)
\r
481 sb.Append(delimiter);
\r
488 val.ConvertToString(sb);
\r
494 case JsonType.Object:
\r
497 foreach (DictionaryEntry entry in _dict)
\r
499 sb.Append(delimiter);
\r
501 sb.Append(entry.Key);
\r
503 if (entry.Value == null)
\r
509 ((JsonObject)entry.Value).ConvertToString(sb);
\r
518 private object Value
\r
524 case JsonType.Bool:
\r
526 case JsonType.Number:
\r
528 case JsonType.String:
\r
535 private enum JsonType
\r
545 public class JsonParserException : Exception
\r
547 public JsonParserException()
\r
551 public JsonParserException(string message) : base(message)
\r