JSONObject.cs 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124
  1. #define PRETTY //Comment out when you no longer need to read JSON to disable pretty Print system-wide
  2. //Using doubles will cause errors in VectorTemplates.cs; Unity speaks floats
  3. #define USEFLOAT //Use floats for numbers instead of doubles (enable if you're getting too many significant digits in string output)
  4. //#define POOLING //Currently using a build setting for this one (also it's experimental)
  5. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  6. using UnityEngine;
  7. using Debug = UnityEngine.Debug;
  8. #endif
  9. using System.Diagnostics;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.Text;
  13. /*
  14. Copyright (c) 2015 Matt Schoen
  15. Permission is hereby granted, free of charge, to any person obtaining a copy
  16. of this software and associated documentation files (the "Software"), to deal
  17. in the Software without restriction, including without limitation the rights
  18. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  19. copies of the Software, and to permit persons to whom the Software is
  20. furnished to do so, subject to the following conditions:
  21. The above copyright notice and this permission notice shall be included in
  22. all copies or substantial portions of the Software.
  23. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  29. THE SOFTWARE.
  30. */
  31. internal class JSONObject {
  32. #if POOLING
  33. const int MAX_POOL_SIZE = 10000;
  34. public static Queue<JSONObject> releaseQueue = new Queue<JSONObject>();
  35. #endif
  36. const int MAX_DEPTH = 100;
  37. const string INFINITY = "\"INFINITY\"";
  38. const string NEGINFINITY = "\"NEGINFINITY\"";
  39. const string NaN = "\"NaN\"";
  40. private static readonly char[] WHITESPACE = { ' ', '\r', '\n', '\t', '\uFEFF', '\u0009' };
  41. public enum Type { NULL, STRING, NUMBER, OBJECT, ARRAY, BOOL, BAKED }
  42. private bool isContainer { get { return (type == Type.ARRAY || type == Type.OBJECT); } }
  43. private Type type = Type.NULL;
  44. public int Count {
  45. get {
  46. if(list == null)
  47. return -1;
  48. return list.Count;
  49. }
  50. }
  51. private List<JSONObject> list;
  52. public List<string> keys;
  53. public string str;
  54. #if USEFLOAT
  55. public float n;
  56. public float f {
  57. get {
  58. return n;
  59. }
  60. }
  61. #else
  62. public double n;
  63. public float f {
  64. get {
  65. return (float)n;
  66. }
  67. }
  68. #endif
  69. private bool useInt;
  70. public long i;
  71. public bool b;
  72. public delegate void AddJSONContents(JSONObject self);
  73. private static JSONObject nullJO { get { return Create(Type.NULL); } } //an empty, null object
  74. private static JSONObject obj { get { return Create(Type.OBJECT); } } //an empty object
  75. private static JSONObject arr { get { return Create(Type.ARRAY); } } //an empty array
  76. public JSONObject(Type t) {
  77. type = t;
  78. switch(t) {
  79. case Type.ARRAY:
  80. list = new List<JSONObject>();
  81. break;
  82. case Type.OBJECT:
  83. list = new List<JSONObject>();
  84. keys = new List<string>();
  85. break;
  86. }
  87. }
  88. private JSONObject(bool b) {
  89. type = Type.BOOL;
  90. this.b = b;
  91. }
  92. #if USEFLOAT
  93. private JSONObject(float f) {
  94. type = Type.NUMBER;
  95. n = f;
  96. }
  97. #else
  98. public JSONObject(double d) {
  99. type = Type.NUMBER;
  100. n = d;
  101. }
  102. #endif
  103. private JSONObject(int i) {
  104. type = Type.NUMBER;
  105. this.i = i;
  106. useInt = true;
  107. n = i;
  108. }
  109. private JSONObject(long l) {
  110. type = Type.NUMBER;
  111. i = l;
  112. useInt = true;
  113. n = l;
  114. }
  115. private JSONObject(Dictionary<string, string> dic) {
  116. type = Type.OBJECT;
  117. keys = new List<string>();
  118. list = new List<JSONObject>();
  119. //Not sure if it's worth removing the foreach here
  120. foreach(KeyValuePair<string, string> kvp in dic) {
  121. keys.Add(kvp.Key);
  122. list.Add(CreateStringObject(kvp.Value));
  123. }
  124. }
  125. private JSONObject(Dictionary<string, JSONObject> dic) {
  126. type = Type.OBJECT;
  127. keys = new List<string>();
  128. list = new List<JSONObject>();
  129. //Not sure if it's worth removing the foreach here
  130. foreach(KeyValuePair<string, JSONObject> kvp in dic) {
  131. keys.Add(kvp.Key);
  132. list.Add(kvp.Value);
  133. }
  134. }
  135. private JSONObject(AddJSONContents content) {
  136. content.Invoke(this);
  137. }
  138. private JSONObject(JSONObject[] objs) {
  139. type = Type.ARRAY;
  140. list = new List<JSONObject>(objs);
  141. }
  142. //Convenience function for creating a JSONObject containing a string. This is not part of the constructor so that malformed JSON data doesn't just turn into a string object
  143. private static JSONObject StringObject(string val) { return CreateStringObject(val); }
  144. private void Absorb(JSONObject obj) {
  145. list.AddRange(obj.list);
  146. keys.AddRange(obj.keys);
  147. str = obj.str;
  148. n = obj.n;
  149. useInt = obj.useInt;
  150. i = obj.i;
  151. b = obj.b;
  152. type = obj.type;
  153. }
  154. public static JSONObject Create() {
  155. #if POOLING
  156. JSONObject result = null;
  157. while(result == null && releaseQueue.Count > 0) {
  158. result = releaseQueue.Dequeue();
  159. #if DEV
  160. //The following cases should NEVER HAPPEN (but they do...)
  161. if(result == null)
  162. Debug.WriteLine("wtf " + releaseQueue.Count);
  163. else if(result.list != null)
  164. Debug.WriteLine("wtflist " + result.list.Count);
  165. #endif
  166. }
  167. if(result != null)
  168. return result;
  169. #endif
  170. return new JSONObject();
  171. }
  172. public static JSONObject Create(Type t) {
  173. JSONObject obj = Create();
  174. obj.type = t;
  175. switch(t) {
  176. case Type.ARRAY:
  177. obj.list = new List<JSONObject>();
  178. break;
  179. case Type.OBJECT:
  180. obj.list = new List<JSONObject>();
  181. obj.keys = new List<string>();
  182. break;
  183. }
  184. return obj;
  185. }
  186. public static JSONObject Create(bool val) {
  187. JSONObject obj = Create();
  188. obj.type = Type.BOOL;
  189. obj.b = val;
  190. return obj;
  191. }
  192. public static JSONObject Create(float val) {
  193. JSONObject obj = Create();
  194. obj.type = Type.NUMBER;
  195. obj.n = val;
  196. return obj;
  197. }
  198. public static JSONObject Create(int val) {
  199. JSONObject obj = Create();
  200. obj.type = Type.NUMBER;
  201. obj.n = val;
  202. obj.useInt = true;
  203. obj.i = val;
  204. return obj;
  205. }
  206. public static JSONObject Create(long val) {
  207. JSONObject obj = Create();
  208. obj.type = Type.NUMBER;
  209. obj.n = val;
  210. obj.useInt = true;
  211. obj.i = val;
  212. return obj;
  213. }
  214. public static JSONObject CreateStringObject(string val) {
  215. JSONObject obj = Create();
  216. obj.type = Type.STRING;
  217. obj.str = val;
  218. return obj;
  219. }
  220. public static JSONObject CreateBakedObject(string val) {
  221. JSONObject bakedObject = Create();
  222. bakedObject.type = Type.BAKED;
  223. bakedObject.str = val;
  224. return bakedObject;
  225. }
  226. /// <summary>
  227. /// Create a JSONObject by parsing string data
  228. /// </summary>
  229. /// <param name="val">The string to be parsed</param>
  230. /// <param name="maxDepth">The maximum depth for the parser to search. Set this to to 1 for the first level,
  231. /// 2 for the first 2 levels, etc. It defaults to -2 because -1 is the depth value that is parsed (see below)</param>
  232. /// <param name="storeExcessLevels">Whether to store levels beyond maxDepth in baked JSONObjects</param>
  233. /// <param name="strict">Whether to be strict in the parsing. For example, non-strict parsing will successfully
  234. /// parse "a string" into a string-type </param>
  235. /// <returns></returns>
  236. public static JSONObject Create(string val, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) {
  237. JSONObject obj = Create();
  238. obj.Parse(val, maxDepth, storeExcessLevels, strict);
  239. return obj;
  240. }
  241. private static JSONObject Create(AddJSONContents content) {
  242. JSONObject obj = Create();
  243. content.Invoke(obj);
  244. return obj;
  245. }
  246. private static JSONObject Create(Dictionary<string, string> dic) {
  247. JSONObject obj = Create();
  248. obj.type = Type.OBJECT;
  249. obj.keys = new List<string>();
  250. obj.list = new List<JSONObject>();
  251. //Not sure if it's worth removing the foreach here
  252. foreach(KeyValuePair<string, string> kvp in dic) {
  253. obj.keys.Add(kvp.Key);
  254. obj.list.Add(CreateStringObject(kvp.Value));
  255. }
  256. return obj;
  257. }
  258. private JSONObject() { }
  259. #region PARSE
  260. public JSONObject(string str, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { //create a new JSONObject from a string (this will also create any children, and parse the whole string)
  261. Parse(str, maxDepth, storeExcessLevels, strict);
  262. }
  263. void Parse(string str, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) {
  264. if(!string.IsNullOrEmpty(str)) {
  265. str = str.Trim(WHITESPACE);
  266. if(strict) {
  267. if(str[0] != '[' && str[0] != '{') {
  268. type = Type.NULL;
  269. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  270. Debug.LogWarning
  271. #else
  272. Debug.WriteLine
  273. #endif
  274. ("Improper (strict) JSON formatting. First character must be [ or {");
  275. return;
  276. }
  277. }
  278. if(str.Length > 0) {
  279. #if UNITY_WP8 || UNITY_WSA
  280. if (str == "true") {
  281. type = Type.BOOL;
  282. b = true;
  283. } else if (str == "false") {
  284. type = Type.BOOL;
  285. b = false;
  286. } else if (str == "null") {
  287. type = Type.NULL;
  288. #else
  289. if(string.Compare(str, "true", true) == 0) {
  290. type = Type.BOOL;
  291. b = true;
  292. } else if(string.Compare(str, "false", true) == 0) {
  293. type = Type.BOOL;
  294. b = false;
  295. } else if(string.Compare(str, "null", true) == 0) {
  296. type = Type.NULL;
  297. #endif
  298. #if USEFLOAT
  299. } else if(str == INFINITY) {
  300. type = Type.NUMBER;
  301. n = float.PositiveInfinity;
  302. } else if(str == NEGINFINITY) {
  303. type = Type.NUMBER;
  304. n = float.NegativeInfinity;
  305. } else if(str == NaN) {
  306. type = Type.NUMBER;
  307. n = float.NaN;
  308. #else
  309. } else if(str == INFINITY) {
  310. type = Type.NUMBER;
  311. n = double.PositiveInfinity;
  312. } else if(str == NEGINFINITY) {
  313. type = Type.NUMBER;
  314. n = double.NegativeInfinity;
  315. } else if(str == NaN) {
  316. type = Type.NUMBER;
  317. n = double.NaN;
  318. #endif
  319. } else if(str[0] == '"') {
  320. type = Type.STRING;
  321. this.str = str.Substring(1, str.Length - 2);
  322. } else {
  323. int tokenTmp = 1;
  324. /*
  325. * Checking for the following formatting (www.json.org)
  326. * object - {"field1":value,"field2":value}
  327. * array - [value,value,value]
  328. * value - string - "string"
  329. * - number - 0.0
  330. * - bool - true -or- false
  331. * - null - null
  332. */
  333. int offset = 0;
  334. switch(str[offset]) {
  335. case '{':
  336. type = Type.OBJECT;
  337. keys = new List<string>();
  338. list = new List<JSONObject>();
  339. break;
  340. case '[':
  341. type = Type.ARRAY;
  342. list = new List<JSONObject>();
  343. break;
  344. default:
  345. try {
  346. #if USEFLOAT
  347. n = System.Convert.ToSingle(str);
  348. #else
  349. n = System.Convert.ToDouble(str);
  350. #endif
  351. if(!str.Contains(".")) {
  352. i = System.Convert.ToInt64(str);
  353. useInt = true;
  354. }
  355. type = Type.NUMBER;
  356. } catch(System.FormatException) {
  357. type = Type.NULL;
  358. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  359. Debug.LogWarning
  360. #else
  361. Debug.WriteLine
  362. #endif
  363. ("improper JSON formatting:" + str);
  364. }
  365. return;
  366. }
  367. string propName = "";
  368. bool openQuote = false;
  369. bool inProp = false;
  370. int depth = 0;
  371. while(++offset < str.Length) {
  372. if(System.Array.IndexOf(WHITESPACE, str[offset]) > -1)
  373. continue;
  374. if(str[offset] == '\\') {
  375. offset += 1;
  376. continue;
  377. }
  378. if(str[offset] == '"') {
  379. if(openQuote) {
  380. if(!inProp && depth == 0 && type == Type.OBJECT)
  381. propName = str.Substring(tokenTmp + 1, offset - tokenTmp - 1);
  382. openQuote = false;
  383. } else {
  384. if(depth == 0 && type == Type.OBJECT)
  385. tokenTmp = offset;
  386. openQuote = true;
  387. }
  388. }
  389. if(openQuote)
  390. continue;
  391. if(type == Type.OBJECT && depth == 0) {
  392. if(str[offset] == ':') {
  393. tokenTmp = offset + 1;
  394. inProp = true;
  395. }
  396. }
  397. if(str[offset] == '[' || str[offset] == '{') {
  398. depth++;
  399. } else if(str[offset] == ']' || str[offset] == '}') {
  400. depth--;
  401. }
  402. //if (encounter a ',' at top level) || a closing ]/}
  403. if((str[offset] == ',' && depth == 0) || depth < 0) {
  404. inProp = false;
  405. string inner = str.Substring(tokenTmp, offset - tokenTmp).Trim(WHITESPACE);
  406. if(inner.Length > 0) {
  407. if(type == Type.OBJECT)
  408. keys.Add(propName);
  409. if(maxDepth != -1) //maxDepth of -1 is the end of the line
  410. list.Add(Create(inner, (maxDepth < -1) ? -2 : maxDepth - 1));
  411. else if(storeExcessLevels)
  412. list.Add(CreateBakedObject(inner));
  413. }
  414. tokenTmp = offset + 1;
  415. }
  416. }
  417. }
  418. } else type = Type.NULL;
  419. } else type = Type.NULL; //If the string is missing, this is a null
  420. //Profiler.EndSample();
  421. }
  422. #endregion
  423. private bool IsNumber { get { return type == Type.NUMBER; } }
  424. private bool IsNull { get { return type == Type.NULL; } }
  425. private bool IsString { get { return type == Type.STRING; } }
  426. private bool IsBool { get { return type == Type.BOOL; } }
  427. private bool IsArray { get { return type == Type.ARRAY; } }
  428. private bool IsObject { get { return type == Type.OBJECT || type == Type.BAKED; } }
  429. public void Add(bool val) {
  430. Add(Create(val));
  431. }
  432. public void Add(float val) {
  433. Add(Create(val));
  434. }
  435. public void Add(int val) {
  436. Add(Create(val));
  437. }
  438. public void Add(string str) {
  439. Add(CreateStringObject(str));
  440. }
  441. private void Add(AddJSONContents content) {
  442. Add(Create(content));
  443. }
  444. public void Add(JSONObject obj) {
  445. if(obj) { //Don't do anything if the object is null
  446. if(type != Type.ARRAY) {
  447. type = Type.ARRAY; //Congratulations, son, you're an ARRAY now
  448. if(list == null)
  449. list = new List<JSONObject>();
  450. }
  451. list.Add(obj);
  452. }
  453. }
  454. public void AddField(string name, bool val) {
  455. AddField(name, Create(val));
  456. }
  457. public void AddField(string name, float val) {
  458. AddField(name, Create(val));
  459. }
  460. public void AddField(string name, int val) {
  461. AddField(name, Create(val));
  462. }
  463. private void AddField(string name, long val) {
  464. AddField(name, Create(val));
  465. }
  466. private void AddField(string name, AddJSONContents content) {
  467. AddField(name, Create(content));
  468. }
  469. public void AddField(string name, string val) {
  470. AddField(name, CreateStringObject(val));
  471. }
  472. public void AddField(string name, JSONObject obj) {
  473. if(obj) { //Don't do anything if the object is null
  474. if(type != Type.OBJECT) {
  475. if(keys == null)
  476. keys = new List<string>();
  477. if(type == Type.ARRAY) {
  478. for(int i = 0; i < list.Count; i++)
  479. keys.Add(i + "");
  480. } else
  481. if(list == null)
  482. list = new List<JSONObject>();
  483. type = Type.OBJECT; //Congratulations, son, you're an OBJECT now
  484. }
  485. keys.Add(name);
  486. list.Add(obj);
  487. }
  488. }
  489. public void SetField(string name, string val) { SetField(name, CreateStringObject(val)); }
  490. private void SetField(string name, bool val) { SetField(name, Create(val)); }
  491. private void SetField(string name, float val) { SetField(name, Create(val)); }
  492. private void SetField(string name, int val) { SetField(name, Create(val)); }
  493. private void SetField(string name, JSONObject obj) {
  494. if(HasField(name)) {
  495. list.Remove(this[name]);
  496. keys.Remove(name);
  497. }
  498. AddField(name, obj);
  499. }
  500. private void RemoveField(string name) {
  501. if(keys.IndexOf(name) > -1) {
  502. list.RemoveAt(keys.IndexOf(name));
  503. keys.Remove(name);
  504. }
  505. }
  506. private delegate void FieldNotFound(string name);
  507. private delegate void GetFieldResponse(JSONObject obj);
  508. private bool GetField(out bool field, string name, bool fallback) {
  509. field = fallback;
  510. return GetField(ref field, name);
  511. }
  512. private bool GetField(ref bool field, string name, FieldNotFound fail = null) {
  513. if(type == Type.OBJECT) {
  514. int index = keys.IndexOf(name);
  515. if(index >= 0) {
  516. field = list[index].b;
  517. return true;
  518. }
  519. }
  520. if(fail != null) fail.Invoke(name);
  521. return false;
  522. }
  523. #if USEFLOAT
  524. private bool GetField(out float field, string name, float fallback) {
  525. #else
  526. public bool GetField(out double field, string name, double fallback) {
  527. #endif
  528. field = fallback;
  529. return GetField(ref field, name);
  530. }
  531. #if USEFLOAT
  532. private bool GetField(ref float field, string name, FieldNotFound fail = null) {
  533. #else
  534. public bool GetField(ref double field, string name, FieldNotFound fail = null) {
  535. #endif
  536. if(type == Type.OBJECT) {
  537. int index = keys.IndexOf(name);
  538. if(index >= 0) {
  539. field = list[index].n;
  540. return true;
  541. }
  542. }
  543. if(fail != null) fail.Invoke(name);
  544. return false;
  545. }
  546. private bool GetField(out int field, string name, int fallback) {
  547. field = fallback;
  548. return GetField(ref field, name);
  549. }
  550. private bool GetField(ref int field, string name, FieldNotFound fail = null) {
  551. if(IsObject) {
  552. int index = keys.IndexOf(name);
  553. if(index >= 0) {
  554. field = (int)list[index].n;
  555. return true;
  556. }
  557. }
  558. if(fail != null) fail.Invoke(name);
  559. return false;
  560. }
  561. private bool GetField(out long field, string name, long fallback) {
  562. field = fallback;
  563. return GetField(ref field, name);
  564. }
  565. private bool GetField(ref long field, string name, FieldNotFound fail = null) {
  566. if(IsObject) {
  567. int index = keys.IndexOf(name);
  568. if(index >= 0) {
  569. field = (long)list[index].n;
  570. return true;
  571. }
  572. }
  573. if(fail != null) fail.Invoke(name);
  574. return false;
  575. }
  576. private bool GetField(out uint field, string name, uint fallback) {
  577. field = fallback;
  578. return GetField(ref field, name);
  579. }
  580. private bool GetField(ref uint field, string name, FieldNotFound fail = null) {
  581. if(IsObject) {
  582. int index = keys.IndexOf(name);
  583. if(index >= 0) {
  584. field = (uint)list[index].n;
  585. return true;
  586. }
  587. }
  588. if(fail != null) fail.Invoke(name);
  589. return false;
  590. }
  591. private bool GetField(out string field, string name, string fallback) {
  592. field = fallback;
  593. return GetField(ref field, name);
  594. }
  595. private bool GetField(ref string field, string name, FieldNotFound fail = null) {
  596. if(IsObject) {
  597. int index = keys.IndexOf(name);
  598. if(index >= 0) {
  599. field = list[index].str;
  600. return true;
  601. }
  602. }
  603. if(fail != null) fail.Invoke(name);
  604. return false;
  605. }
  606. private void GetField(string name, GetFieldResponse response, FieldNotFound fail = null) {
  607. if(response != null && IsObject) {
  608. int index = keys.IndexOf(name);
  609. if(index >= 0) {
  610. response.Invoke(list[index]);
  611. return;
  612. }
  613. }
  614. if(fail != null) fail.Invoke(name);
  615. }
  616. public JSONObject GetField(string name) {
  617. if(IsObject)
  618. for(int i = 0; i < keys.Count; i++)
  619. if(keys[i] == name)
  620. return list[i];
  621. return null;
  622. }
  623. private bool HasFields(string[] names) {
  624. if(!IsObject)
  625. return false;
  626. for(int i = 0; i < names.Length; i++)
  627. if(!keys.Contains(names[i]))
  628. return false;
  629. return true;
  630. }
  631. private bool HasField(string name) {
  632. if(!IsObject)
  633. return false;
  634. for(int i = 0; i < keys.Count; i++)
  635. if(keys[i] == name)
  636. return true;
  637. return false;
  638. }
  639. public void Clear() {
  640. type = Type.NULL;
  641. if(list != null)
  642. list.Clear();
  643. if(keys != null)
  644. keys.Clear();
  645. str = "";
  646. n = 0;
  647. b = false;
  648. }
  649. /// <summary>
  650. /// Copy a JSONObject. This could probably work better
  651. /// </summary>
  652. /// <returns></returns>
  653. private JSONObject Copy() {
  654. return Create(Print());
  655. }
  656. /*
  657. * The Merge function is experimental. Use at your own risk.
  658. */
  659. private void Merge(JSONObject obj) {
  660. MergeRecur(this, obj);
  661. }
  662. /// <summary>
  663. /// Merge object right into left recursively
  664. /// </summary>
  665. /// <param name="left">The left (base) object</param>
  666. /// <param name="right">The right (new) object</param>
  667. static void MergeRecur(JSONObject left, JSONObject right) {
  668. if(left.type == Type.NULL)
  669. left.Absorb(right);
  670. else if(left.type == Type.OBJECT && right.type == Type.OBJECT) {
  671. for(int i = 0; i < right.list.Count; i++) {
  672. string key = right.keys[i];
  673. if(right[i].isContainer) {
  674. if(left.HasField(key))
  675. MergeRecur(left[key], right[i]);
  676. else
  677. left.AddField(key, right[i]);
  678. } else {
  679. if(left.HasField(key))
  680. left.SetField(key, right[i]);
  681. else
  682. left.AddField(key, right[i]);
  683. }
  684. }
  685. } else if(left.type == Type.ARRAY && right.type == Type.ARRAY) {
  686. if(right.Count > left.Count) {
  687. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  688. Debug.LogError
  689. #else
  690. Debug.WriteLine
  691. #endif
  692. ("Cannot merge arrays when right object has more elements");
  693. return;
  694. }
  695. for(int i = 0; i < right.list.Count; i++) {
  696. if(left[i].type == right[i].type) { //Only overwrite with the same type
  697. if(left[i].isContainer)
  698. MergeRecur(left[i], right[i]);
  699. else {
  700. left[i] = right[i];
  701. }
  702. }
  703. }
  704. }
  705. }
  706. private void Bake() {
  707. if(type != Type.BAKED) {
  708. str = Print();
  709. type = Type.BAKED;
  710. }
  711. }
  712. private IEnumerable BakeAsync() {
  713. if(type != Type.BAKED) {
  714. foreach(string s in PrintAsync()) {
  715. if(s == null)
  716. yield return s;
  717. else {
  718. str = s;
  719. }
  720. }
  721. type = Type.BAKED;
  722. }
  723. }
  724. #pragma warning disable 219
  725. public string Print(bool pretty = true) {
  726. StringBuilder builder = new StringBuilder();
  727. Stringify(0, builder, pretty);
  728. return builder.ToString();
  729. }
  730. private IEnumerable<string> PrintAsync(bool pretty = false) {
  731. StringBuilder builder = new StringBuilder();
  732. printWatch.Reset();
  733. printWatch.Start();
  734. foreach(IEnumerable e in StringifyAsync(0, builder, pretty)) {
  735. yield return null;
  736. }
  737. yield return builder.ToString();
  738. }
  739. #pragma warning restore 219
  740. #region STRINGIFY
  741. const float maxFrameTime = 0.008f;
  742. static readonly Stopwatch printWatch = new Stopwatch();
  743. IEnumerable StringifyAsync(int depth, StringBuilder builder, bool pretty = false) { //Convert the JSONObject into a string
  744. //Profiler.BeginSample("JSONprint");
  745. if(depth++ > MAX_DEPTH) {
  746. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  747. Debug.Log
  748. #else
  749. Debug.WriteLine
  750. #endif
  751. ("reached max depth!");
  752. yield break;
  753. }
  754. if(printWatch.Elapsed.TotalSeconds > maxFrameTime) {
  755. printWatch.Reset();
  756. yield return null;
  757. printWatch.Start();
  758. }
  759. switch(type) {
  760. case Type.BAKED:
  761. builder.Append(str);
  762. break;
  763. case Type.STRING:
  764. builder.AppendFormat("\"{0}\"", str);
  765. break;
  766. case Type.NUMBER:
  767. if(useInt) {
  768. builder.Append(i.ToString());
  769. } else {
  770. #if USEFLOAT
  771. if(float.IsInfinity(n))
  772. builder.Append(INFINITY);
  773. else if(float.IsNegativeInfinity(n))
  774. builder.Append(NEGINFINITY);
  775. else if(float.IsNaN(n))
  776. builder.Append(NaN);
  777. #else
  778. if(double.IsInfinity(n))
  779. builder.Append(INFINITY);
  780. else if(double.IsNegativeInfinity(n))
  781. builder.Append(NEGINFINITY);
  782. else if(double.IsNaN(n))
  783. builder.Append(NaN);
  784. #endif
  785. else
  786. builder.Append(n.ToString());
  787. }
  788. break;
  789. case Type.OBJECT:
  790. builder.Append("{");
  791. if(list.Count > 0) {
  792. #if(PRETTY) //for a bit more readability, comment the define above to disable system-wide
  793. if(pretty)
  794. builder.Append("\n");
  795. #endif
  796. for(int i = 0; i < list.Count; i++) {
  797. string key = keys[i];
  798. JSONObject obj = list[i];
  799. if(obj) {
  800. #if(PRETTY)
  801. if(pretty)
  802. for(int j = 0; j < depth; j++)
  803. builder.Append("\t"); //for a bit more readability
  804. #endif
  805. builder.AppendFormat("\"{0}\":", key);
  806. foreach(IEnumerable e in obj.StringifyAsync(depth, builder, pretty))
  807. yield return e;
  808. builder.Append(",");
  809. #if(PRETTY)
  810. if(pretty)
  811. builder.Append("\n");
  812. #endif
  813. }
  814. }
  815. #if(PRETTY)
  816. if(pretty)
  817. builder.Length -= 2;
  818. else
  819. #endif
  820. builder.Length--;
  821. }
  822. #if(PRETTY)
  823. if(pretty && list.Count > 0) {
  824. builder.Append("\n");
  825. for(int j = 0; j < depth - 1; j++)
  826. builder.Append("\t"); //for a bit more readability
  827. }
  828. #endif
  829. builder.Append("}");
  830. break;
  831. case Type.ARRAY:
  832. builder.Append("[");
  833. if(list.Count > 0) {
  834. #if(PRETTY)
  835. if(pretty)
  836. builder.Append("\n"); //for a bit more readability
  837. #endif
  838. for(int i = 0; i < list.Count; i++) {
  839. if(list[i]) {
  840. #if(PRETTY)
  841. if(pretty)
  842. for(int j = 0; j < depth; j++)
  843. builder.Append("\t"); //for a bit more readability
  844. #endif
  845. foreach(IEnumerable e in list[i].StringifyAsync(depth, builder, pretty))
  846. yield return e;
  847. builder.Append(",");
  848. #if(PRETTY)
  849. if(pretty)
  850. builder.Append("\n"); //for a bit more readability
  851. #endif
  852. }
  853. }
  854. #if(PRETTY)
  855. if(pretty)
  856. builder.Length -= 2;
  857. else
  858. #endif
  859. builder.Length--;
  860. }
  861. #if(PRETTY)
  862. if(pretty && list.Count > 0) {
  863. builder.Append("\n");
  864. for(int j = 0; j < depth - 1; j++)
  865. builder.Append("\t"); //for a bit more readability
  866. }
  867. #endif
  868. builder.Append("]");
  869. break;
  870. case Type.BOOL:
  871. if(b)
  872. builder.Append("true");
  873. else
  874. builder.Append("false");
  875. break;
  876. case Type.NULL:
  877. builder.Append("null");
  878. break;
  879. }
  880. //Profiler.EndSample();
  881. }
  882. //TODO: Refactor Stringify functions to share core logic
  883. /*
  884. * I know, I know, this is really bad form. It turns out that there is a
  885. * significant amount of garbage created when calling as a coroutine, so this
  886. * method is duplicated. Hopefully there won't be too many future changes, but
  887. * I would still like a more elegant way to optionaly yield
  888. */
  889. void Stringify(int depth, StringBuilder builder, bool pretty = true) { //Convert the JSONObject into a string
  890. //Profiler.BeginSample("JSONprint");
  891. if(depth++ > MAX_DEPTH) {
  892. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  893. Debug.Log
  894. #else
  895. Debug.WriteLine
  896. #endif
  897. ("reached max depth!");
  898. return;
  899. }
  900. switch(type) {
  901. case Type.BAKED:
  902. builder.Append(str);
  903. break;
  904. case Type.STRING:
  905. builder.AppendFormat("\"{0}\"", str);
  906. break;
  907. case Type.NUMBER:
  908. if(useInt) {
  909. builder.Append(i.ToString());
  910. } else {
  911. #if USEFLOAT
  912. if(float.IsInfinity(n))
  913. builder.Append(INFINITY);
  914. else if(float.IsNegativeInfinity(n))
  915. builder.Append(NEGINFINITY);
  916. else if(float.IsNaN(n))
  917. builder.Append(NaN);
  918. #else
  919. if(double.IsInfinity(n))
  920. builder.Append(INFINITY);
  921. else if(double.IsNegativeInfinity(n))
  922. builder.Append(NEGINFINITY);
  923. else if(double.IsNaN(n))
  924. builder.Append(NaN);
  925. #endif
  926. else
  927. builder.Append(n.ToString());
  928. }
  929. break;
  930. case Type.OBJECT:
  931. builder.Append("{");
  932. if(list.Count > 0) {
  933. #if(PRETTY) //for a bit more readability, comment the define above to disable system-wide
  934. if(pretty)
  935. builder.Append("\n");
  936. #endif
  937. for(int i = 0; i < list.Count; i++) {
  938. string key = keys[i];
  939. JSONObject obj = list[i];
  940. if(obj) {
  941. #if(PRETTY)
  942. if(pretty)
  943. for(int j = 0; j < depth; j++)
  944. builder.Append("\t"); //for a bit more readability
  945. #endif
  946. builder.AppendFormat("\"{0}\":", key);
  947. obj.Stringify(depth, builder, pretty);
  948. builder.Append(",");
  949. #if(PRETTY)
  950. if(pretty)
  951. builder.Append("\n");
  952. #endif
  953. }
  954. }
  955. #if(PRETTY)
  956. if(pretty)
  957. builder.Length -= 2;
  958. else
  959. #endif
  960. builder.Length--;
  961. }
  962. #if(PRETTY)
  963. if(pretty && list.Count > 0) {
  964. builder.Append("\n");
  965. for(int j = 0; j < depth - 1; j++)
  966. builder.Append("\t"); //for a bit more readability
  967. }
  968. #endif
  969. builder.Append("}");
  970. break;
  971. case Type.ARRAY:
  972. builder.Append("[");
  973. if(list.Count > 0) {
  974. #if(PRETTY)
  975. if(pretty)
  976. builder.Append("\n"); //for a bit more readability
  977. #endif
  978. for(int i = 0; i < list.Count; i++) {
  979. if(list[i]) {
  980. #if(PRETTY)
  981. if(pretty)
  982. for(int j = 0; j < depth; j++)
  983. builder.Append("\t"); //for a bit more readability
  984. #endif
  985. list[i].Stringify(depth, builder, pretty);
  986. builder.Append(",");
  987. #if(PRETTY)
  988. if(pretty)
  989. builder.Append("\n"); //for a bit more readability
  990. #endif
  991. }
  992. }
  993. #if(PRETTY)
  994. if(pretty)
  995. builder.Length -= 2;
  996. else
  997. #endif
  998. builder.Length--;
  999. }
  1000. #if(PRETTY)
  1001. if(pretty && list.Count > 0) {
  1002. builder.Append("\n");
  1003. for(int j = 0; j < depth - 1; j++)
  1004. builder.Append("\t"); //for a bit more readability
  1005. }
  1006. #endif
  1007. builder.Append("]");
  1008. break;
  1009. case Type.BOOL:
  1010. if(b)
  1011. builder.Append("true");
  1012. else
  1013. builder.Append("false");
  1014. break;
  1015. case Type.NULL:
  1016. builder.Append("null");
  1017. break;
  1018. }
  1019. //Profiler.EndSample();
  1020. }
  1021. #endregion
  1022. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  1023. public static implicit operator WWWForm(JSONObject obj) {
  1024. WWWForm form = new WWWForm();
  1025. for(int i = 0; i < obj.list.Count; i++) {
  1026. string key = i + "";
  1027. if(obj.type == Type.OBJECT)
  1028. key = obj.keys[i];
  1029. string val = obj.list[i].ToString();
  1030. if(obj.list[i].type == Type.STRING)
  1031. val = val.Replace("\"", "");
  1032. form.AddField(key, val);
  1033. }
  1034. return form;
  1035. }
  1036. #endif
  1037. public JSONObject this[int index] {
  1038. get {
  1039. if(list.Count > index) return list[index];
  1040. return null;
  1041. }
  1042. set {
  1043. if(list.Count > index)
  1044. list[index] = value;
  1045. }
  1046. }
  1047. public JSONObject this[string index] {
  1048. get {
  1049. return GetField(index);
  1050. }
  1051. set {
  1052. SetField(index, value);
  1053. }
  1054. }
  1055. public override string ToString() {
  1056. return Print();
  1057. }
  1058. private string ToString(bool pretty) {
  1059. return Print(pretty);
  1060. }
  1061. private Dictionary<string, string> ToDictionary() {
  1062. if(type == Type.OBJECT) {
  1063. Dictionary<string, string> result = new Dictionary<string, string>();
  1064. for(int i = 0; i < list.Count; i++) {
  1065. JSONObject val = list[i];
  1066. switch(val.type) {
  1067. case Type.STRING: result.Add(keys[i], val.str); break;
  1068. case Type.NUMBER: result.Add(keys[i], val.n + ""); break;
  1069. case Type.BOOL: result.Add(keys[i], val.b + ""); break;
  1070. default:
  1071. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  1072. Debug.LogWarning
  1073. #else
  1074. Debug.WriteLine
  1075. #endif
  1076. ("Omitting object: " + keys[i] + " in dictionary conversion");
  1077. break;
  1078. }
  1079. }
  1080. return result;
  1081. }
  1082. #if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5
  1083. Debug.Log
  1084. #else
  1085. Debug.WriteLine
  1086. #endif
  1087. ("Tried to turn non-Object JSONObject into a dictionary");
  1088. return null;
  1089. }
  1090. public static implicit operator bool(JSONObject o) {
  1091. return o != null;
  1092. }
  1093. #if POOLING
  1094. static bool pool = true;
  1095. public static void ClearPool() {
  1096. pool = false;
  1097. releaseQueue.Clear();
  1098. pool = true;
  1099. }
  1100. ~JSONObject() {
  1101. if(pool && releaseQueue.Count < MAX_POOL_SIZE) {
  1102. type = Type.NULL;
  1103. list = null;
  1104. keys = null;
  1105. str = "";
  1106. n = 0;
  1107. b = false;
  1108. releaseQueue.Enqueue(this);
  1109. }
  1110. }
  1111. #endif
  1112. }