/* 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)
{
}
}
}
}