TinyJson.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Pathfinding.WindowsStore;
  4. using System;
  5. #if NETFX_CORE
  6. using System.Linq;
  7. using WinRTLegacy;
  8. #endif
  9. namespace Pathfinding.Serialization {
  10. public class JsonMemberAttribute : System.Attribute {
  11. }
  12. public class JsonOptInAttribute : System.Attribute {
  13. }
  14. /// <summary>
  15. /// A very tiny json serializer.
  16. /// It is not supposed to have lots of features, it is only intended to be able to serialize graph settings
  17. /// well enough.
  18. /// </summary>
  19. public class TinyJsonSerializer {
  20. System.Text.StringBuilder output = new System.Text.StringBuilder();
  21. Dictionary<Type, Action<System.Object> > serializers = new Dictionary<Type, Action<object> >();
  22. static readonly System.Globalization.CultureInfo invariantCulture = System.Globalization.CultureInfo.InvariantCulture;
  23. public static void Serialize (System.Object obj, System.Text.StringBuilder output) {
  24. new TinyJsonSerializer() {
  25. output = output
  26. }.Serialize(obj);
  27. }
  28. TinyJsonSerializer () {
  29. serializers[typeof(float)] = v => output.Append(((float)v).ToString("R", invariantCulture));
  30. serializers[typeof(bool)] = v => output.Append((bool)v ? "true" : "false");
  31. serializers[typeof(Version)] = serializers[typeof(uint)] = serializers[typeof(int)] = v => output.Append(v.ToString());
  32. serializers[typeof(string)] = v => output.AppendFormat("\"{0}\"", v.ToString().Replace("\"", "\\\""));
  33. serializers[typeof(Vector2)] = v => output.AppendFormat("{{ \"x\": {0}, \"y\": {1} }}", ((Vector2)v).x.ToString("R", invariantCulture), ((Vector2)v).y.ToString("R", invariantCulture));
  34. 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));
  35. serializers[typeof(Pathfinding.Util.Guid)] = v => output.AppendFormat("{{ \"value\": \"{0}\" }}", v.ToString());
  36. serializers[typeof(LayerMask)] = v => output.AppendFormat("{{ \"value\": {0} }}", ((int)(LayerMask)v).ToString());
  37. }
  38. void Serialize (System.Object obj) {
  39. if (obj == null) {
  40. output.Append("null");
  41. return;
  42. }
  43. var type = obj.GetType();
  44. var typeInfo = WindowsStoreCompatibility.GetTypeInfo(type);
  45. if (serializers.ContainsKey(type)) {
  46. serializers[type] (obj);
  47. } else if (typeInfo.IsEnum) {
  48. output.Append('"' + obj.ToString() + '"');
  49. } else if (obj is System.Collections.IList) {
  50. output.Append("[");
  51. var arr = obj as System.Collections.IList;
  52. for (int i = 0; i < arr.Count; i++) {
  53. if (i != 0)
  54. output.Append(", ");
  55. Serialize(arr[i]);
  56. }
  57. output.Append("]");
  58. } else if (obj is UnityEngine.Object) {
  59. SerializeUnityObject(obj as UnityEngine.Object);
  60. } else {
  61. #if NETFX_CORE
  62. var optIn = typeInfo.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonOptInAttribute));
  63. #else
  64. var optIn = typeInfo.GetCustomAttributes(typeof(JsonOptInAttribute), true).Length > 0;
  65. #endif
  66. output.Append("{");
  67. bool earlier = false;
  68. while (true) {
  69. #if NETFX_CORE
  70. var fields = typeInfo.DeclaredFields.Where(f => !f.IsStatic).ToArray();
  71. #else
  72. var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
  73. #endif
  74. foreach (var field in fields) {
  75. if (field.DeclaringType != type) continue;
  76. if ((!optIn && field.IsPublic) ||
  77. #if NETFX_CORE
  78. field.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonMemberAttribute))
  79. #else
  80. field.GetCustomAttributes(typeof(JsonMemberAttribute), true).Length > 0
  81. #endif
  82. ) {
  83. if (earlier) {
  84. output.Append(", ");
  85. }
  86. earlier = true;
  87. output.AppendFormat("\"{0}\": ", field.Name);
  88. Serialize(field.GetValue(obj));
  89. }
  90. }
  91. #if NETFX_CORE
  92. typeInfo = typeInfo.BaseType;
  93. if (typeInfo == null) break;
  94. #else
  95. type = type.BaseType;
  96. if (type == null) break;
  97. #endif
  98. }
  99. output.Append("}");
  100. }
  101. }
  102. void QuotedField (string name, string contents) {
  103. output.AppendFormat("\"{0}\": \"{1}\"", name, contents);
  104. }
  105. void SerializeUnityObject (UnityEngine.Object obj) {
  106. // Note that a unityengine can be destroyed as well
  107. if (obj == null) {
  108. Serialize(null);
  109. return;
  110. }
  111. output.Append("{");
  112. var path = obj.name;
  113. #if UNITY_EDITOR
  114. // Figure out the path of the object relative to a Resources folder.
  115. // In a standalone player this cannot be done unfortunately, so we will assume it is at the top level in the Resources folder.
  116. // Fortunately it should be extremely rare to have to serialize references to unity objects in a standalone player.
  117. var realPath = UnityEditor.AssetDatabase.GetAssetPath(obj);
  118. var match = System.Text.RegularExpressions.Regex.Match(realPath, @"Resources/(.*?)(\.\w+)?$");
  119. if (match != null) path = match.Groups[1].Value;
  120. #endif
  121. QuotedField("Name", path);
  122. output.Append(", ");
  123. QuotedField("Type", obj.GetType().FullName);
  124. //Write scene path if the object is a Component or GameObject
  125. var component = obj as Component;
  126. var go = obj as GameObject;
  127. if (component != null || go != null) {
  128. if (component != null && go == null) {
  129. go = component.gameObject;
  130. }
  131. var helper = go.GetComponent<UnityReferenceHelper>();
  132. if (helper == null) {
  133. Debug.Log("Adding UnityReferenceHelper to Unity Reference '"+obj.name+"'");
  134. helper = go.AddComponent<UnityReferenceHelper>();
  135. }
  136. //Make sure it has a unique GUID
  137. helper.Reset();
  138. output.Append(", ");
  139. QuotedField("GUID", helper.GetGUID().ToString());
  140. }
  141. output.Append("}");
  142. }
  143. }
  144. /// <summary>
  145. /// A very tiny json deserializer.
  146. /// It is not supposed to have lots of features, it is only intended to be able to deserialize graph settings
  147. /// well enough. Not much validation of the input is done.
  148. /// </summary>
  149. public class TinyJsonDeserializer {
  150. System.IO.TextReader reader;
  151. GameObject contextRoot;
  152. static readonly System.Globalization.NumberFormatInfo numberFormat = System.Globalization.NumberFormatInfo.InvariantInfo;
  153. /// <summary>
  154. /// Deserializes an object of the specified type.
  155. /// Will load all fields into the populate object if it is set (only works for classes).
  156. /// </summary>
  157. public static System.Object Deserialize (string text, Type type, System.Object populate = null, GameObject contextRoot = null) {
  158. return new TinyJsonDeserializer() {
  159. reader = new System.IO.StringReader(text),
  160. contextRoot = contextRoot,
  161. }.Deserialize(type, populate);
  162. }
  163. /// <summary>
  164. /// Deserializes an object of type tp.
  165. /// Will load all fields into the populate object if it is set (only works for classes).
  166. /// </summary>
  167. System.Object Deserialize (Type tp, System.Object populate = null) {
  168. var tpInfo = WindowsStoreCompatibility.GetTypeInfo(tp);
  169. if (tpInfo.IsEnum) {
  170. return Enum.Parse(tp, EatField());
  171. } else if (TryEat('n')) {
  172. Eat("ull");
  173. TryEat(',');
  174. return null;
  175. } else if (Type.Equals(tp, typeof(float))) {
  176. return float.Parse(EatField(), numberFormat);
  177. } else if (Type.Equals(tp, typeof(int))) {
  178. return int.Parse(EatField(), numberFormat);
  179. } else if (Type.Equals(tp, typeof(uint))) {
  180. return uint.Parse(EatField(), numberFormat);
  181. } else if (Type.Equals(tp, typeof(bool))) {
  182. return bool.Parse(EatField());
  183. } else if (Type.Equals(tp, typeof(string))) {
  184. return EatField();
  185. } else if (Type.Equals(tp, typeof(Version))) {
  186. return new Version(EatField());
  187. } else if (Type.Equals(tp, typeof(Vector2))) {
  188. Eat("{");
  189. var result = new Vector2();
  190. EatField();
  191. result.x = float.Parse(EatField(), numberFormat);
  192. EatField();
  193. result.y = float.Parse(EatField(), numberFormat);
  194. Eat("}");
  195. return result;
  196. } else if (Type.Equals(tp, typeof(Vector3))) {
  197. Eat("{");
  198. var result = new Vector3();
  199. EatField();
  200. result.x = float.Parse(EatField(), numberFormat);
  201. EatField();
  202. result.y = float.Parse(EatField(), numberFormat);
  203. EatField();
  204. result.z = float.Parse(EatField(), numberFormat);
  205. Eat("}");
  206. return result;
  207. } else if (Type.Equals(tp, typeof(Pathfinding.Util.Guid))) {
  208. Eat("{");
  209. EatField();
  210. var result = Pathfinding.Util.Guid.Parse(EatField());
  211. Eat("}");
  212. return result;
  213. } else if (Type.Equals(tp, typeof(LayerMask))) {
  214. Eat("{");
  215. EatField();
  216. var result = (LayerMask)int.Parse(EatField());
  217. Eat("}");
  218. return result;
  219. } else if (Type.Equals(tp, typeof(List<string>))) {
  220. System.Collections.IList result = new List<string>();
  221. Eat("[");
  222. while (!TryEat(']')) {
  223. result.Add(Deserialize(typeof(string)));
  224. TryEat(',');
  225. }
  226. return result;
  227. } else if (tpInfo.IsArray) {
  228. List<System.Object> ls = new List<System.Object>();
  229. Eat("[");
  230. while (!TryEat(']')) {
  231. ls.Add(Deserialize(tp.GetElementType()));
  232. TryEat(',');
  233. }
  234. var arr = Array.CreateInstance(tp.GetElementType(), ls.Count);
  235. ls.ToArray().CopyTo(arr, 0);
  236. return arr;
  237. } else if (Type.Equals(tp, typeof(Mesh)) || Type.Equals(tp, typeof(Texture2D)) || Type.Equals(tp, typeof(Transform)) || Type.Equals(tp, typeof(GameObject))) {
  238. return DeserializeUnityObject();
  239. } else {
  240. var obj = populate ?? Activator.CreateInstance(tp);
  241. Eat("{");
  242. while (!TryEat('}')) {
  243. var name = EatField();
  244. var tmpType = tp;
  245. System.Reflection.FieldInfo field = null;
  246. while (field == null && tmpType != null) {
  247. field = tmpType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
  248. tmpType = tmpType.BaseType;
  249. }
  250. if (field == null) {
  251. SkipFieldData();
  252. } else {
  253. field.SetValue(obj, Deserialize(field.FieldType));
  254. }
  255. TryEat(',');
  256. }
  257. return obj;
  258. }
  259. }
  260. UnityEngine.Object DeserializeUnityObject () {
  261. Eat("{");
  262. var result = DeserializeUnityObjectInner();
  263. Eat("}");
  264. return result;
  265. }
  266. UnityEngine.Object DeserializeUnityObjectInner () {
  267. // Ignore InstanceID field (compatibility only)
  268. var fieldName = EatField();
  269. if (fieldName == "InstanceID") {
  270. EatField();
  271. fieldName = EatField();
  272. }
  273. if (fieldName != "Name") throw new Exception("Expected 'Name' field");
  274. string name = EatField();
  275. if (name == null) return null;
  276. if (EatField() != "Type") throw new Exception("Expected 'Type' field");
  277. string typename = EatField();
  278. // Remove assembly information
  279. if (typename.IndexOf(',') != -1) {
  280. typename = typename.Substring(0, typename.IndexOf(','));
  281. }
  282. // Note calling through assembly is more stable on e.g WebGL
  283. var type = WindowsStoreCompatibility.GetTypeInfo(typeof(AstarPath)).Assembly.GetType(typename);
  284. type = type ?? WindowsStoreCompatibility.GetTypeInfo(typeof(Transform)).Assembly.GetType(typename);
  285. if (Type.Equals(type, null)) {
  286. Debug.LogError("Could not find type '"+typename+"'. Cannot deserialize Unity reference");
  287. return null;
  288. }
  289. // Check if there is another field there
  290. EatWhitespace();
  291. if ((char)reader.Peek() == '"') {
  292. if (EatField() != "GUID") throw new Exception("Expected 'GUID' field");
  293. string guid = EatField();
  294. if (contextRoot != null) {
  295. foreach (var helper in contextRoot.GetComponentsInChildren<UnityReferenceHelper>(true)) {
  296. if (helper.GetGUID() == guid) {
  297. if (Type.Equals(type, typeof(GameObject))) {
  298. return helper.gameObject;
  299. } else {
  300. return helper.GetComponent(type);
  301. }
  302. }
  303. }
  304. }
  305. #if UNITY_2020_1_OR_NEWER
  306. foreach (var helper in UnityEngine.Object.FindObjectsOfType<UnityReferenceHelper>(true))
  307. #else
  308. foreach (var helper in UnityEngine.Object.FindObjectsOfType<UnityReferenceHelper>())
  309. #endif
  310. {
  311. if (helper.GetGUID() == guid) {
  312. if (Type.Equals(type, typeof(GameObject))) {
  313. return helper.gameObject;
  314. } else {
  315. return helper.GetComponent(type);
  316. }
  317. }
  318. }
  319. }
  320. // Note: calling LoadAll with an empty string will make it load the whole resources folder, which is probably a bad idea.
  321. if (!string.IsNullOrEmpty(name)) {
  322. // Try to load from resources
  323. UnityEngine.Object[] objs = Resources.LoadAll(name, type);
  324. for (int i = 0; i < objs.Length; i++) {
  325. if (objs[i].name == name || objs.Length == 1) {
  326. return objs[i];
  327. }
  328. }
  329. }
  330. return null;
  331. }
  332. void EatWhitespace () {
  333. while (char.IsWhiteSpace((char)reader.Peek()))
  334. reader.Read();
  335. }
  336. void Eat (string s) {
  337. EatWhitespace();
  338. for (int i = 0; i < s.Length; i++) {
  339. var c = (char)reader.Read();
  340. if (c != s[i]) {
  341. throw new Exception("Expected '" + s[i] + "' found '" + c + "'\n\n..." + reader.ReadLine());
  342. }
  343. }
  344. }
  345. System.Text.StringBuilder builder = new System.Text.StringBuilder();
  346. string EatUntil (string c, bool inString) {
  347. builder.Length = 0;
  348. bool escape = false;
  349. while (true) {
  350. var readInt = reader.Peek();
  351. if (!escape && (char)readInt == '"') {
  352. inString = !inString;
  353. }
  354. var readChar = (char)readInt;
  355. if (readInt == -1) {
  356. throw new Exception("Unexpected EOF");
  357. } else if (!escape && readChar == '\\') {
  358. escape = true;
  359. reader.Read();
  360. } else if (!inString && c.IndexOf(readChar) != -1) {
  361. break;
  362. } else {
  363. builder.Append(readChar);
  364. reader.Read();
  365. escape = false;
  366. }
  367. }
  368. return builder.ToString();
  369. }
  370. bool TryEat (char c) {
  371. EatWhitespace();
  372. if ((char)reader.Peek() == c) {
  373. reader.Read();
  374. return true;
  375. }
  376. return false;
  377. }
  378. string EatField () {
  379. var result = EatUntil("\",}]", TryEat('"'));
  380. TryEat('\"');
  381. TryEat(':');
  382. TryEat(',');
  383. return result;
  384. }
  385. void SkipFieldData () {
  386. var indent = 0;
  387. while (true) {
  388. EatUntil(",{}[]", false);
  389. var last = (char)reader.Peek();
  390. switch (last) {
  391. case '{':
  392. case '[':
  393. indent++;
  394. break;
  395. case '}':
  396. case ']':
  397. indent--;
  398. if (indent < 0) return;
  399. break;
  400. case ',':
  401. if (indent == 0) {
  402. reader.Read();
  403. return;
  404. }
  405. break;
  406. default:
  407. throw new System.Exception("Should not reach this part");
  408. }
  409. reader.Read();
  410. }
  411. }
  412. }
  413. }