using UnityEngine;
using System.Collections.Generic;
using Pathfinding.WindowsStore;
using System;
#if NETFX_CORE
using System.Linq;
using WinRTLegacy;
#endif
namespace Pathfinding.Serialization {
public class JsonMemberAttribute : System.Attribute {
}
public class JsonOptInAttribute : System.Attribute {
}
///
/// A very tiny json serializer.
/// It is not supposed to have lots of features, it is only intended to be able to serialize graph settings
/// well enough.
///
public class TinyJsonSerializer {
System.Text.StringBuilder output = new System.Text.StringBuilder();
Dictionary > serializers = new Dictionary >();
static readonly System.Globalization.CultureInfo invariantCulture = System.Globalization.CultureInfo.InvariantCulture;
public static void Serialize (System.Object obj, System.Text.StringBuilder output) {
new TinyJsonSerializer() {
output = output
}.Serialize(obj);
}
TinyJsonSerializer () {
serializers[typeof(float)] = v => output.Append(((float)v).ToString("R", invariantCulture));
serializers[typeof(bool)] = v => output.Append((bool)v ? "true" : "false");
serializers[typeof(Version)] = serializers[typeof(uint)] = serializers[typeof(int)] = v => output.Append(v.ToString());
serializers[typeof(string)] = v => output.AppendFormat("\"{0}\"", v.ToString().Replace("\"", "\\\""));
serializers[typeof(Vector2)] = v => output.AppendFormat("{{ \"x\": {0}, \"y\": {1} }}", ((Vector2)v).x.ToString("R", invariantCulture), ((Vector2)v).y.ToString("R", invariantCulture));
serializers[typeof(Vector3)] = v => output.AppendFormat("{{ \"x\": {0}, \"y\": {1}, \"z\": {2} }}", ((Vector3)v).x.ToString("R", invariantCulture), ((Vector3)v).y.ToString("R", invariantCulture), ((Vector3)v).z.ToString("R", invariantCulture));
serializers[typeof(Pathfinding.Util.Guid)] = v => output.AppendFormat("{{ \"value\": \"{0}\" }}", v.ToString());
serializers[typeof(LayerMask)] = v => output.AppendFormat("{{ \"value\": {0} }}", ((int)(LayerMask)v).ToString());
}
void Serialize (System.Object obj) {
if (obj == null) {
output.Append("null");
return;
}
var type = obj.GetType();
var typeInfo = WindowsStoreCompatibility.GetTypeInfo(type);
if (serializers.ContainsKey(type)) {
serializers[type] (obj);
} else if (typeInfo.IsEnum) {
output.Append('"' + obj.ToString() + '"');
} else if (obj is System.Collections.IList) {
output.Append("[");
var arr = obj as System.Collections.IList;
for (int i = 0; i < arr.Count; i++) {
if (i != 0)
output.Append(", ");
Serialize(arr[i]);
}
output.Append("]");
} else if (obj is UnityEngine.Object) {
SerializeUnityObject(obj as UnityEngine.Object);
} else {
#if NETFX_CORE
var optIn = typeInfo.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonOptInAttribute));
#else
var optIn = typeInfo.GetCustomAttributes(typeof(JsonOptInAttribute), true).Length > 0;
#endif
output.Append("{");
bool earlier = false;
while (true) {
#if NETFX_CORE
var fields = typeInfo.DeclaredFields.Where(f => !f.IsStatic).ToArray();
#else
var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
#endif
foreach (var field in fields) {
if (field.DeclaringType != type) continue;
if ((!optIn && field.IsPublic) ||
#if NETFX_CORE
field.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonMemberAttribute))
#else
field.GetCustomAttributes(typeof(JsonMemberAttribute), true).Length > 0
#endif
) {
if (earlier) {
output.Append(", ");
}
earlier = true;
output.AppendFormat("\"{0}\": ", field.Name);
Serialize(field.GetValue(obj));
}
}
#if NETFX_CORE
typeInfo = typeInfo.BaseType;
if (typeInfo == null) break;
#else
type = type.BaseType;
if (type == null) break;
#endif
}
output.Append("}");
}
}
void QuotedField (string name, string contents) {
output.AppendFormat("\"{0}\": \"{1}\"", name, contents);
}
void SerializeUnityObject (UnityEngine.Object obj) {
// Note that a unityengine can be destroyed as well
if (obj == null) {
Serialize(null);
return;
}
output.Append("{");
var path = obj.name;
#if UNITY_EDITOR
// Figure out the path of the object relative to a Resources folder.
// In a standalone player this cannot be done unfortunately, so we will assume it is at the top level in the Resources folder.
// Fortunately it should be extremely rare to have to serialize references to unity objects in a standalone player.
var realPath = UnityEditor.AssetDatabase.GetAssetPath(obj);
var match = System.Text.RegularExpressions.Regex.Match(realPath, @"Resources/(.*?)(\.\w+)?$");
if (match != null) path = match.Groups[1].Value;
#endif
QuotedField("Name", path);
output.Append(", ");
QuotedField("Type", obj.GetType().FullName);
//Write scene path if the object is a Component or GameObject
var component = obj as Component;
var go = obj as GameObject;
if (component != null || go != null) {
if (component != null && go == null) {
go = component.gameObject;
}
var helper = go.GetComponent();
if (helper == null) {
Debug.Log("Adding UnityReferenceHelper to Unity Reference '"+obj.name+"'");
helper = go.AddComponent();
}
//Make sure it has a unique GUID
helper.Reset();
output.Append(", ");
QuotedField("GUID", helper.GetGUID().ToString());
}
output.Append("}");
}
}
///
/// A very tiny json deserializer.
/// It is not supposed to have lots of features, it is only intended to be able to deserialize graph settings
/// well enough. Not much validation of the input is done.
///
public class TinyJsonDeserializer {
System.IO.TextReader reader;
GameObject contextRoot;
static readonly System.Globalization.NumberFormatInfo numberFormat = System.Globalization.NumberFormatInfo.InvariantInfo;
///
/// Deserializes an object of the specified type.
/// Will load all fields into the populate object if it is set (only works for classes).
///
public static System.Object Deserialize (string text, Type type, System.Object populate = null, GameObject contextRoot = null) {
return new TinyJsonDeserializer() {
reader = new System.IO.StringReader(text),
contextRoot = contextRoot,
}.Deserialize(type, populate);
}
///
/// Deserializes an object of type tp.
/// Will load all fields into the populate object if it is set (only works for classes).
///
System.Object Deserialize (Type tp, System.Object populate = null) {
var tpInfo = WindowsStoreCompatibility.GetTypeInfo(tp);
if (tpInfo.IsEnum) {
return Enum.Parse(tp, EatField());
} else if (TryEat('n')) {
Eat("ull");
TryEat(',');
return null;
} else if (Type.Equals(tp, typeof(float))) {
return float.Parse(EatField(), numberFormat);
} else if (Type.Equals(tp, typeof(int))) {
return int.Parse(EatField(), numberFormat);
} else if (Type.Equals(tp, typeof(uint))) {
return uint.Parse(EatField(), numberFormat);
} else if (Type.Equals(tp, typeof(bool))) {
return bool.Parse(EatField());
} else if (Type.Equals(tp, typeof(string))) {
return EatField();
} else if (Type.Equals(tp, typeof(Version))) {
return new Version(EatField());
} else if (Type.Equals(tp, typeof(Vector2))) {
Eat("{");
var result = new Vector2();
EatField();
result.x = float.Parse(EatField(), numberFormat);
EatField();
result.y = float.Parse(EatField(), numberFormat);
Eat("}");
return result;
} else if (Type.Equals(tp, typeof(Vector3))) {
Eat("{");
var result = new Vector3();
EatField();
result.x = float.Parse(EatField(), numberFormat);
EatField();
result.y = float.Parse(EatField(), numberFormat);
EatField();
result.z = float.Parse(EatField(), numberFormat);
Eat("}");
return result;
} else if (Type.Equals(tp, typeof(Pathfinding.Util.Guid))) {
Eat("{");
EatField();
var result = Pathfinding.Util.Guid.Parse(EatField());
Eat("}");
return result;
} else if (Type.Equals(tp, typeof(LayerMask))) {
Eat("{");
EatField();
var result = (LayerMask)int.Parse(EatField());
Eat("}");
return result;
} else if (Type.Equals(tp, typeof(List))) {
System.Collections.IList result = new List();
Eat("[");
while (!TryEat(']')) {
result.Add(Deserialize(typeof(string)));
TryEat(',');
}
return result;
} else if (tpInfo.IsArray) {
List ls = new List();
Eat("[");
while (!TryEat(']')) {
ls.Add(Deserialize(tp.GetElementType()));
TryEat(',');
}
var arr = Array.CreateInstance(tp.GetElementType(), ls.Count);
ls.ToArray().CopyTo(arr, 0);
return arr;
} else if (Type.Equals(tp, typeof(Mesh)) || Type.Equals(tp, typeof(Texture2D)) || Type.Equals(tp, typeof(Transform)) || Type.Equals(tp, typeof(GameObject))) {
return DeserializeUnityObject();
} else {
var obj = populate ?? Activator.CreateInstance(tp);
Eat("{");
while (!TryEat('}')) {
var name = EatField();
var tmpType = tp;
System.Reflection.FieldInfo field = null;
while (field == null && tmpType != null) {
field = tmpType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
tmpType = tmpType.BaseType;
}
if (field == null) {
SkipFieldData();
} else {
field.SetValue(obj, Deserialize(field.FieldType));
}
TryEat(',');
}
return obj;
}
}
UnityEngine.Object DeserializeUnityObject () {
Eat("{");
var result = DeserializeUnityObjectInner();
Eat("}");
return result;
}
UnityEngine.Object DeserializeUnityObjectInner () {
// Ignore InstanceID field (compatibility only)
var fieldName = EatField();
if (fieldName == "InstanceID") {
EatField();
fieldName = EatField();
}
if (fieldName != "Name") throw new Exception("Expected 'Name' field");
string name = EatField();
if (name == null) return null;
if (EatField() != "Type") throw new Exception("Expected 'Type' field");
string typename = EatField();
// Remove assembly information
if (typename.IndexOf(',') != -1) {
typename = typename.Substring(0, typename.IndexOf(','));
}
// Note calling through assembly is more stable on e.g WebGL
var type = WindowsStoreCompatibility.GetTypeInfo(typeof(AstarPath)).Assembly.GetType(typename);
type = type ?? WindowsStoreCompatibility.GetTypeInfo(typeof(Transform)).Assembly.GetType(typename);
if (Type.Equals(type, null)) {
Debug.LogError("Could not find type '"+typename+"'. Cannot deserialize Unity reference");
return null;
}
// Check if there is another field there
EatWhitespace();
if ((char)reader.Peek() == '"') {
if (EatField() != "GUID") throw new Exception("Expected 'GUID' field");
string guid = EatField();
if (contextRoot != null) {
foreach (var helper in contextRoot.GetComponentsInChildren(true)) {
if (helper.GetGUID() == guid) {
if (Type.Equals(type, typeof(GameObject))) {
return helper.gameObject;
} else {
return helper.GetComponent(type);
}
}
}
}
#if UNITY_2020_1_OR_NEWER
foreach (var helper in UnityEngine.Object.FindObjectsOfType(true))
#else
foreach (var helper in UnityEngine.Object.FindObjectsOfType())
#endif
{
if (helper.GetGUID() == guid) {
if (Type.Equals(type, typeof(GameObject))) {
return helper.gameObject;
} else {
return helper.GetComponent(type);
}
}
}
}
// Note: calling LoadAll with an empty string will make it load the whole resources folder, which is probably a bad idea.
if (!string.IsNullOrEmpty(name)) {
// Try to load from resources
UnityEngine.Object[] objs = Resources.LoadAll(name, type);
for (int i = 0; i < objs.Length; i++) {
if (objs[i].name == name || objs.Length == 1) {
return objs[i];
}
}
}
return null;
}
void EatWhitespace () {
while (char.IsWhiteSpace((char)reader.Peek()))
reader.Read();
}
void Eat (string s) {
EatWhitespace();
for (int i = 0; i < s.Length; i++) {
var c = (char)reader.Read();
if (c != s[i]) {
throw new Exception("Expected '" + s[i] + "' found '" + c + "'\n\n..." + reader.ReadLine());
}
}
}
System.Text.StringBuilder builder = new System.Text.StringBuilder();
string EatUntil (string c, bool inString) {
builder.Length = 0;
bool escape = false;
while (true) {
var readInt = reader.Peek();
if (!escape && (char)readInt == '"') {
inString = !inString;
}
var readChar = (char)readInt;
if (readInt == -1) {
throw new Exception("Unexpected EOF");
} else if (!escape && readChar == '\\') {
escape = true;
reader.Read();
} else if (!inString && c.IndexOf(readChar) != -1) {
break;
} else {
builder.Append(readChar);
reader.Read();
escape = false;
}
}
return builder.ToString();
}
bool TryEat (char c) {
EatWhitespace();
if ((char)reader.Peek() == c) {
reader.Read();
return true;
}
return false;
}
string EatField () {
var result = EatUntil("\",}]", TryEat('"'));
TryEat('\"');
TryEat(':');
TryEat(',');
return result;
}
void SkipFieldData () {
var indent = 0;
while (true) {
EatUntil(",{}[]", false);
var last = (char)reader.Peek();
switch (last) {
case '{':
case '[':
indent++;
break;
case '}':
case ']':
indent--;
if (indent < 0) return;
break;
case ',':
if (indent == 0) {
reader.Read();
return;
}
break;
default:
throw new System.Exception("Should not reach this part");
}
reader.Read();
}
}
}
}