/* INFINITY CODE 2013-2019 */ /* http://www.infinity-code.com */ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Text; using InfinityCode.RealWorldTerrain.Utils; using UnityEngine; namespace InfinityCode.RealWorldTerrain.JSON { /// /// Class for working with JSON. It is used for parsing of string, serialization and deserialization of object. /// public class RealWorldTerrainJson { private string json; private int index = 0; private Token lookAheadToken = Token.None; private StringBuilder s; private int length; protected RealWorldTerrainJson(string json) { s = new StringBuilder(); this.json = json; length = json.Length; } /// /// Deserialize string into object. /// /// Type /// JSON string /// Deserialized object public static T Deserialize(string json) { object obj = ParseDirect(json); if (obj is IDictionary) return (T) DeserializeObject(typeof(T), obj as Dictionary); if (obj is IList) return (T) DeserializeArray(typeof(T), obj as List); return (T) DeserializeValue(typeof(T), obj); } private static object DeserializeValue(Type type, object obj) { if (obj == null) return null; try { return Convert.ChangeType(obj, type); } catch (Exception exception) { Debug.Log(exception.Message + "\n" + exception.StackTrace); } return null; } private static object DeserializeArray(Type type, List list) { if (list == null || list.Count == 0) return null; if (type.IsArray) { Type elementType = type.GetElementType(); Array v = Array.CreateInstance(elementType, list.Count); for (int i = 0; i < list.Count; i++) { object child = list[i]; object item; if (child is IDictionary) item = DeserializeObject(elementType, child as Dictionary); else if (child is IList) item = DeserializeArray(elementType, child as List); else item = DeserializeValue(elementType, child); v.SetValue(item, i); } return v; } if (RealWorldTerrainReflectionHelper.IsGenericType(type)) { Type listType = RealWorldTerrainReflectionHelper.GetGenericArguments(type)[0]; object v = Activator.CreateInstance(type); for (int i = 0; i < list.Count; i++) { object child = list[i]; object item; if (child is IDictionary) item = DeserializeObject(listType, child as Dictionary); else if (child is IList) item = DeserializeArray(listType, child as List); else item = DeserializeValue(listType, child); try { MethodInfo methodInfo = RealWorldTerrainReflectionHelper.GetMethod(type, "Add"); if (methodInfo != null) methodInfo.Invoke(v, new[] {item}); } catch { } } return v; } return null; } private static object DeserializeObject(Type type, Dictionary table) { IEnumerable members = RealWorldTerrainReflectionHelper.GetMembers(type, BindingFlags.Instance | BindingFlags.Public); object v = Activator.CreateInstance(type); foreach (MemberInfo member in members) { #if !NETFX_CORE MemberTypes memberType = member.MemberType; if (memberType != MemberTypes.Field && memberType != MemberTypes.Property) continue; #else MemberTypes memberType; if (member is PropertyInfo) memberType = MemberTypes.Property; else if (member is FieldInfo) memberType = MemberTypes.Field; else continue; #endif if (memberType == MemberTypes.Property && !((PropertyInfo) member).CanWrite) continue; object item; #if !NETFX_CORE object[] attributes = member.GetCustomAttributes(typeof(AliasAttribute), true); AliasAttribute alias = attributes.Length > 0 ? attributes[0] as AliasAttribute : null; #else IEnumerable attributes = member.GetCustomAttributes(typeof(AliasAttribute), true); AliasAttribute alias = null; foreach (Attribute a in attributes) { alias = a as AliasAttribute; break; } #endif if (alias == null || !alias.ignoreFieldName) { if (table.TryGetValue(member.Name, out item)) { DeserializeValue(memberType, member, item, v); continue; } } if (alias != null) { for (int j = 0; j < alias.aliases.Length; j++) { if (table.TryGetValue(alias.aliases[j], out item)) { DeserializeValue(memberType, member, item, v); break; } } } } return v; } private static void DeserializeValue(MemberTypes memberType, MemberInfo member, object item, object v) { object cv; Type t = memberType == MemberTypes.Field ? ((FieldInfo) member).FieldType : ((PropertyInfo) member).PropertyType; if (t == typeof(System.Object)) cv = item; else if (item is IDictionary) cv = DeserializeObject(t, item as Dictionary); else if (item is IList) cv = DeserializeArray(t, item as List); else cv = DeserializeValue(t, item); if (memberType == MemberTypes.Field) ((FieldInfo) member).SetValue(v, cv); else ((PropertyInfo) member).SetValue(v, cv, null); } private Token LookAhead() { if (lookAheadToken != Token.None) return lookAheadToken; return lookAheadToken = NextTokenCore(); } private Token NextToken() { Token result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore(); lookAheadToken = Token.None; return result; } private Token NextTokenCore() { char c; do { c = json[index]; if (c == '/' && json[index + 1] == '/') { index += 2; do { c = json[index]; if (c == '\r' || c == '\n') break; } while (++index < length); } if (c > ' ') break; if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break; } while (++index < length); if (index == length) throw new Exception("Reached end of string unexpectedly"); c = json[index]; index++; switch (c) { case '{': return Token.Curly_Open; case '}': return Token.Curly_Close; case '[': return Token.Squared_Open; case ']': return Token.Squared_Close; case ',': return Token.Comma; case '"': return Token.String; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '+': case '.': return Token.Number; case ':': return Token.Colon; case 'f': if (length - index >= 4 && json[index + 0] == 'a' && json[index + 1] == 'l' && json[index + 2] == 's' && json[index + 3] == 'e') { index += 4; return Token.False; } break; case 't': if (length - index >= 3 && json[index + 0] == 'r' && json[index + 1] == 'u' && json[index + 2] == 'e') { index += 3; return Token.True; } break; case 'n': if (length - index >= 3 && json[index + 0] == 'u' && json[index + 1] == 'l' && json[index + 2] == 'l') { index += 3; return Token.Null; } break; } throw new Exception("Could not find token at index " + --index); } /// /// Parse JSON string into RealWorldTerrainJsonItem /// /// JSON string /// Root object public static RealWorldTerrainJsonItem Parse(string json) { RealWorldTerrainJson instance = new RealWorldTerrainJson(json); return instance.ParseValue(); } /// /// Parse JSON string into Dictonary, List and Object /// /// JSON string /// Root object public static object ParseDirect(string json) { RealWorldTerrainJson instance = new RealWorldTerrainJson(json); return instance.ParseValueDirect(); } private RealWorldTerrainJsonArray ParseArray() { RealWorldTerrainJsonArray array = new RealWorldTerrainJsonArray(); lookAheadToken = Token.None; while (true) { switch (LookAhead()) { case Token.Comma: lookAheadToken = Token.None; break; case Token.Squared_Close: lookAheadToken = Token.None; return array; default: array.Add(ParseValue()); break; } } } private List ParseArrayDirect() { List array = new List(); lookAheadToken = Token.None; while (true) { switch (LookAhead()) { case Token.Comma: lookAheadToken = Token.None; break; case Token.Squared_Close: lookAheadToken = Token.None; return array; default: array.Add(ParseValueDirect()); break; } } } private object ParseNumber() { lookAheadToken = Token.None; index--; long n = 0; bool neg = false; long decimalV = 0; long exp = 0; bool negExp = false; while (index < length) { char c = json[index]; if (c >= '0' && c <= '9') { n = n * 10 + (c - '0'); decimalV *= 10; } else if (c == '.') { decimalV = 1; } else if (c == '-') neg = true; else if (c == '+') neg = false; else if (c == 'e' || c == 'E') { if (decimalV == 0) decimalV = 1; index++; exp = 0; while (index < length) { c = json[index]; if (c >= '0' && c <= '9') exp = exp * 10 + (c - '0'); else if (c == '-') negExp = true; else if (c == '+') negExp = false; else break; index++; } break; } else break; index++; } if (neg) n = -n; if (decimalV != 0) { double v = n / (double) decimalV; if (exp > 0) { if (negExp) v /= Math.Pow(10, exp); else v *= Math.Pow(10, exp); } return v; } return n; } private RealWorldTerrainJsonObject ParseObject() { RealWorldTerrainJsonObject obj = new RealWorldTerrainJsonObject(); lookAheadToken = Token.None; while (true) { switch (LookAhead()) { case Token.Comma: lookAheadToken = Token.None; break; case Token.Curly_Close: lookAheadToken = Token.None; return obj; default: { string name = ParseString(); if (NextToken() != Token.Colon) throw new Exception("Expected colon at index " + index); obj.Add(name, ParseValue()); } break; } } } private Dictionary ParseObjectDirect() { Dictionary obj = new Dictionary(); lookAheadToken = Token.None; while (true) { switch (LookAhead()) { case Token.Comma: lookAheadToken = Token.None; break; case Token.Curly_Close: lookAheadToken = Token.None; return obj; default: { string name = ParseString(); if (NextToken() != Token.Colon) throw new Exception("Expected colon at index " + index); obj.Add(name, ParseValueDirect()); } break; } } } private uint ParseSingleChar(char c1, uint multipliyer) { uint p1 = 0; if (c1 >= '0' && c1 <= '9') p1 = (uint) (c1 - '0') * multipliyer; else if (c1 >= 'A' && c1 <= 'F') p1 = (uint) (c1 - 'A' + 10) * multipliyer; else if (c1 >= 'a' && c1 <= 'f') p1 = (uint) (c1 - 'a' + 10) * multipliyer; return p1; } private string ParseString() { lookAheadToken = Token.None; s.Length = 0; int runIndex = -1; int l = length; string p = json; { while (index < l) { char c = p[index++]; if (c == '"') { if (runIndex != -1) { if (s.Length == 0) return p.Substring(runIndex, index - runIndex - 1); s.Append(p, runIndex, index - runIndex - 1); } return s.ToString(); } if (c != '\\') { if (runIndex == -1) runIndex = index - 1; continue; } if (index == l) break; if (runIndex != -1) { s.Append(p, runIndex, index - runIndex - 1); runIndex = -1; } switch (p[index++]) { case '"': s.Append('"'); break; case '\\': s.Append('\\'); break; case '/': s.Append('/'); break; case 'b': s.Append('\b'); break; case 'f': s.Append('\f'); break; case 'n': s.Append('\n'); break; case 'r': s.Append('\r'); break; case 't': s.Append('\t'); break; case 'u': { int remainingLength = l - index; if (remainingLength < 4) break; uint codePoint = ParseUnicode(p[index], p[index + 1], p[index + 2], p[index + 3]); s.Append((char) codePoint); index += 4; } break; } } } throw new Exception("Unexpectedly reached end of string"); } private uint ParseUnicode(char c1, char c2, char c3, char c4) { uint p1 = ParseSingleChar(c1, 0x1000); uint p2 = ParseSingleChar(c2, 0x100); uint p3 = ParseSingleChar(c3, 0x10); uint p4 = ParseSingleChar(c4, 1); return p1 + p2 + p3 + p4; } private RealWorldTerrainJsonItem ParseValue() { switch (LookAhead()) { case Token.Number: object number = ParseNumber(); return new RealWorldTerrainJsonValue(number, number is double ? RealWorldTerrainJsonValue.ValueType.DOUBLE : RealWorldTerrainJsonValue.ValueType.LONG); case Token.String: return new RealWorldTerrainJsonValue(ParseString(), RealWorldTerrainJsonValue.ValueType.STRING); case Token.Curly_Open: return ParseObject(); case Token.Squared_Open: return ParseArray(); case Token.True: lookAheadToken = Token.None; return new RealWorldTerrainJsonValue(true, RealWorldTerrainJsonValue.ValueType.BOOLEAN); case Token.False: lookAheadToken = Token.None; return new RealWorldTerrainJsonValue(false, RealWorldTerrainJsonValue.ValueType.BOOLEAN); case Token.Null: lookAheadToken = Token.None; return new RealWorldTerrainJsonValue(null, RealWorldTerrainJsonValue.ValueType.NULL); } throw new Exception("Unrecognized token at index" + index); } private object ParseValueDirect() { switch (LookAhead()) { case Token.Number: return ParseNumber(); case Token.String: return ParseString(); case Token.Curly_Open: return ParseObjectDirect(); case Token.Squared_Open: return ParseArrayDirect(); case Token.True: lookAheadToken = Token.None; return true; case Token.False: lookAheadToken = Token.None; return false; case Token.Null: lookAheadToken = Token.None; return null; } throw new Exception("Unrecognized token at index" + index); } /// /// Serializes an object to JSON. /// /// Object /// A bitmask comprised of one or more BindingFlags that specify how the search is conducted. /// JSON public static RealWorldTerrainJsonItem Serialize(object obj, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public) { #if !UNITY_WP_8_1 || UNITY_EDITOR if (obj == null || obj is DBNull) return new RealWorldTerrainJsonValue(obj, RealWorldTerrainJsonValue.ValueType.NULL); #else if (obj == null) return new RealWorldTerrainJsonValue(obj, RealWorldTerrainJsonValue.ValueType.NULL); #endif if (obj is string || obj is bool || obj is int || obj is long || obj is short || obj is float || obj is double) return new RealWorldTerrainJsonValue(obj); if (obj is UnityEngine.Object) { if (!(obj is Component || obj is ScriptableObject)) return new RealWorldTerrainJsonValue((obj as UnityEngine.Object).GetInstanceID()); } if (obj is IDictionary) { IDictionary d = obj as IDictionary; RealWorldTerrainJsonObject dv = new RealWorldTerrainJsonObject(); ICollection keys = d.Keys; ICollection values = d.Values; IEnumerator keysEnum = keys.GetEnumerator(); IEnumerator valuesEnum = values.GetEnumerator(); while (keysEnum.MoveNext() && valuesEnum.MoveNext()) { object k = keysEnum.Current; object v = valuesEnum.Current; dv.Add(k as string, Serialize(v, bindingFlags)); } return dv; } if (obj is IEnumerable) { IEnumerable v = (IEnumerable) obj; RealWorldTerrainJsonArray array = new RealWorldTerrainJsonArray(); foreach (var item in v) array.Add(Serialize(item, bindingFlags)); return array; } RealWorldTerrainJsonObject o = new RealWorldTerrainJsonObject(); Type type = obj.GetType(); if (RealWorldTerrainReflectionHelper.CheckIfAnonymousType(type)) bindingFlags |= BindingFlags.NonPublic; IEnumerable fields = RealWorldTerrainReflectionHelper.GetFields(type, bindingFlags); foreach (FieldInfo field in fields) { string fieldName = field.Name; if (field.Attributes == (FieldAttributes.Private | FieldAttributes.InitOnly)) { int startIndex = fieldName.IndexOf('<') + 1; int endIndex = fieldName.IndexOf('>', startIndex); if (endIndex != -1 && startIndex != -1) fieldName = fieldName.Substring(startIndex, endIndex - startIndex); else fieldName = fieldName.Trim('<', '>'); } o.Add(fieldName, Serialize(field.GetValue(obj))); } return o; } private enum Token { None = -1, Curly_Open, Curly_Close, Squared_Open, Squared_Close, Colon, Comma, String, Number, True, False, Null } /// /// Alias of field used during deserialization. /// public class AliasAttribute : Attribute { /// /// Aliases /// public readonly string[] aliases; /// /// If true, the original field name will be ignored. /// public readonly bool ignoreFieldName; /// /// Constructor /// /// If true, the original field name will be ignored. /// Aliases public AliasAttribute(bool ignoreFieldName, params string[] aliases) { if (aliases == null || aliases.Length == 0) throw new Exception("You must use at least one alias."); this.ignoreFieldName = ignoreFieldName; this.aliases = aliases; } /// /// Constructor /// /// Aliases public AliasAttribute(params string[] aliases) : this(false, aliases) { } } } }