You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1120 lines
29 KiB
1120 lines
29 KiB
/** |
|
* This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus). |
|
* It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE) |
|
*/ |
|
|
|
#define PRETTY //Comment out when you no longer need to read JSON to disable pretty Print system-wide |
|
//Using doubles will cause errors in VectorTemplates.cs; Unity speaks floats |
|
#define USEFLOAT //Use floats for numbers instead of doubles (enable if you're getting too many significant digits in string output) |
|
//#define POOLING //Currently using a build setting for this one (also it's experimental) |
|
|
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
using UnityEngine; |
|
using Debug = UnityEngine.Debug; |
|
#endif |
|
using System.Diagnostics; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Text; |
|
/* |
|
* http://www.opensource.org/licenses/lgpl-2.1.php |
|
* JSONObject class v.1.4.1 |
|
* for use with Unity |
|
* Copyright Matt Schoen 2010 - 2013 |
|
*/ |
|
|
|
// Added to Fungus namespace to minimize conflicts with other assets |
|
namespace Fungus |
|
{ |
|
|
|
public class JSONObject { |
|
#if POOLING |
|
const int MAX_POOL_SIZE = 10000; |
|
public static Queue<JSONObject> releaseQueue = new Queue<JSONObject>(); |
|
#endif |
|
|
|
const int MAX_DEPTH = 100; |
|
const string INFINITY = "\"INFINITY\""; |
|
const string NEGINFINITY = "\"NEGINFINITY\""; |
|
const string NaN = "\"NaN\""; |
|
const string NEWLINE = "\r\n"; |
|
public static readonly char[] WHITESPACE = { ' ', '\r', '\n', '\t', '\uFEFF', '\u0009' }; |
|
public enum Type { NULL, STRING, NUMBER, OBJECT, ARRAY, BOOL, BAKED } |
|
public bool isContainer { get { return (type == Type.ARRAY || type == Type.OBJECT); } } |
|
public Type type = Type.NULL; |
|
public int Count { |
|
get { |
|
if(list == null) |
|
return -1; |
|
return list.Count; |
|
} |
|
} |
|
public List<JSONObject> list; |
|
public List<string> keys; |
|
public string str; |
|
#if USEFLOAT |
|
public float n; |
|
public float f { |
|
get { |
|
return n; |
|
} |
|
} |
|
#else |
|
public double n; |
|
public float f { |
|
get { |
|
return (float)n; |
|
} |
|
} |
|
#endif |
|
public bool useInt; |
|
public long i; |
|
public bool b; |
|
public delegate void AddJSONContents(JSONObject self); |
|
|
|
public static JSONObject nullJO { get { return Create(Type.NULL); } } //an empty, null object |
|
public static JSONObject obj { get { return Create(Type.OBJECT); } } //an empty object |
|
public static JSONObject arr { get { return Create(Type.ARRAY); } } //an empty array |
|
|
|
public JSONObject(Type t) { |
|
type = t; |
|
switch(t) { |
|
case Type.ARRAY: |
|
list = new List<JSONObject>(); |
|
break; |
|
case Type.OBJECT: |
|
list = new List<JSONObject>(); |
|
keys = new List<string>(); |
|
break; |
|
} |
|
} |
|
public JSONObject(bool b) { |
|
type = Type.BOOL; |
|
this.b = b; |
|
} |
|
#if USEFLOAT |
|
public JSONObject(float f) { |
|
type = Type.NUMBER; |
|
n = f; |
|
} |
|
#else |
|
public JSONObject(double d) { |
|
type = Type.NUMBER; |
|
n = d; |
|
} |
|
#endif |
|
public JSONObject(int i) { |
|
type = Type.NUMBER; |
|
this.i = i; |
|
useInt = true; |
|
n = i; |
|
} |
|
public JSONObject(long l) { |
|
type = Type.NUMBER; |
|
i = l; |
|
useInt = true; |
|
n = l; |
|
} |
|
public JSONObject(Dictionary<string, string> dic) { |
|
type = Type.OBJECT; |
|
keys = new List<string>(); |
|
list = new List<JSONObject>(); |
|
//Not sure if it's worth removing the foreach here |
|
foreach(KeyValuePair<string, string> kvp in dic) { |
|
keys.Add(kvp.Key); |
|
list.Add(CreateStringObject(kvp.Value)); |
|
} |
|
} |
|
public JSONObject(Dictionary<string, JSONObject> dic) { |
|
type = Type.OBJECT; |
|
keys = new List<string>(); |
|
list = new List<JSONObject>(); |
|
//Not sure if it's worth removing the foreach here |
|
foreach(KeyValuePair<string, JSONObject> kvp in dic) { |
|
keys.Add(kvp.Key); |
|
list.Add(kvp.Value); |
|
} |
|
} |
|
public JSONObject(AddJSONContents content) { |
|
content.Invoke(this); |
|
} |
|
public JSONObject(JSONObject[] objs) { |
|
type = Type.ARRAY; |
|
list = new List<JSONObject>(objs); |
|
} |
|
//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 |
|
public static JSONObject StringObject(string val) { return CreateStringObject(val); } |
|
public void Absorb(JSONObject obj) { |
|
list.AddRange(obj.list); |
|
keys.AddRange(obj.keys); |
|
str = obj.str; |
|
n = obj.n; |
|
useInt = obj.useInt; |
|
i = obj.i; |
|
b = obj.b; |
|
type = obj.type; |
|
} |
|
public static JSONObject Create() { |
|
#if POOLING |
|
JSONObject result = null; |
|
while(result == null && releaseQueue.Count > 0) { |
|
result = releaseQueue.Dequeue(); |
|
#if DEV |
|
//The following cases should NEVER HAPPEN (but they do...) |
|
if(result == null) |
|
Debug.WriteLine("wtf " + releaseQueue.Count); |
|
else if(result.list != null) |
|
Debug.WriteLine("wtflist " + result.list.Count); |
|
#endif |
|
} |
|
if(result != null) |
|
return result; |
|
#endif |
|
return new JSONObject(); |
|
} |
|
public static JSONObject Create(Type t) { |
|
JSONObject obj = Create(); |
|
obj.type = t; |
|
switch(t) { |
|
case Type.ARRAY: |
|
obj.list = new List<JSONObject>(); |
|
break; |
|
case Type.OBJECT: |
|
obj.list = new List<JSONObject>(); |
|
obj.keys = new List<string>(); |
|
break; |
|
} |
|
return obj; |
|
} |
|
public static JSONObject Create(bool val) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.BOOL; |
|
obj.b = val; |
|
return obj; |
|
} |
|
public static JSONObject Create(float val) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.NUMBER; |
|
obj.n = val; |
|
return obj; |
|
} |
|
public static JSONObject Create(int val) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.NUMBER; |
|
obj.n = val; |
|
obj.useInt = true; |
|
obj.i = val; |
|
return obj; |
|
} |
|
public static JSONObject Create(long val) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.NUMBER; |
|
obj.n = val; |
|
obj.useInt = true; |
|
obj.i = val; |
|
return obj; |
|
} |
|
public static JSONObject CreateStringObject(string val) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.STRING; |
|
obj.str = val; |
|
return obj; |
|
} |
|
public static JSONObject CreateBakedObject(string val) { |
|
JSONObject bakedObject = Create(); |
|
bakedObject.type = Type.BAKED; |
|
bakedObject.str = val; |
|
return bakedObject; |
|
} |
|
/// <summary> |
|
/// Create a JSONObject by parsing string data |
|
/// </summary> |
|
/// <param name="val">The string to be parsed</param> |
|
/// <param name="maxDepth">The maximum depth for the parser to search. Set this to to 1 for the first level, |
|
/// 2 for the first 2 levels, etc. It defaults to -2 because -1 is the depth value that is parsed (see below)</param> |
|
/// <param name="storeExcessLevels">Whether to store levels beyond maxDepth in baked JSONObjects</param> |
|
/// <param name="strict">Whether to be strict in the parsing. For example, non-strict parsing will successfully |
|
/// parse "a string" into a string-type </param> |
|
/// <returns></returns> |
|
public static JSONObject Create(string val, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { |
|
JSONObject obj = Create(); |
|
obj.Parse(val, maxDepth, storeExcessLevels, strict); |
|
return obj; |
|
} |
|
public static JSONObject Create(AddJSONContents content) { |
|
JSONObject obj = Create(); |
|
content.Invoke(obj); |
|
return obj; |
|
} |
|
public static JSONObject Create(Dictionary<string, string> dic) { |
|
JSONObject obj = Create(); |
|
obj.type = Type.OBJECT; |
|
obj.keys = new List<string>(); |
|
obj.list = new List<JSONObject>(); |
|
//Not sure if it's worth removing the foreach here |
|
foreach(KeyValuePair<string, string> kvp in dic) { |
|
obj.keys.Add(kvp.Key); |
|
obj.list.Add(CreateStringObject(kvp.Value)); |
|
} |
|
return obj; |
|
} |
|
public JSONObject() { } |
|
#region PARSE |
|
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) |
|
Parse(str, maxDepth, storeExcessLevels, strict); |
|
} |
|
void Parse(string str, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { |
|
if(!string.IsNullOrEmpty(str)) { |
|
str = str.Trim(WHITESPACE); |
|
if(strict) { |
|
if(str[0] != '[' && str[0] != '{') { |
|
type = Type.NULL; |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.LogWarning |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("Improper (strict) JSON formatting. First character must be [ or {"); |
|
return; |
|
} |
|
} |
|
if(str.Length > 0) { |
|
#if UNITY_WP8 || UNITY_WSA |
|
if (str == "true") { |
|
type = Type.BOOL; |
|
b = true; |
|
} else if (str == "false") { |
|
type = Type.BOOL; |
|
b = false; |
|
} else if (str == "null") { |
|
type = Type.NULL; |
|
#else |
|
if(string.Compare(str, "true", true) == 0) { |
|
type = Type.BOOL; |
|
b = true; |
|
} else if(string.Compare(str, "false", true) == 0) { |
|
type = Type.BOOL; |
|
b = false; |
|
} else if(string.Compare(str, "null", true) == 0) { |
|
type = Type.NULL; |
|
#endif |
|
#if USEFLOAT |
|
} else if(str == INFINITY) { |
|
type = Type.NUMBER; |
|
n = float.PositiveInfinity; |
|
} else if(str == NEGINFINITY) { |
|
type = Type.NUMBER; |
|
n = float.NegativeInfinity; |
|
} else if(str == NaN) { |
|
type = Type.NUMBER; |
|
n = float.NaN; |
|
#else |
|
} else if(str == INFINITY) { |
|
type = Type.NUMBER; |
|
n = double.PositiveInfinity; |
|
} else if(str == NEGINFINITY) { |
|
type = Type.NUMBER; |
|
n = double.NegativeInfinity; |
|
} else if(str == NaN) { |
|
type = Type.NUMBER; |
|
n = double.NaN; |
|
#endif |
|
} else if(str[0] == '"') { |
|
type = Type.STRING; |
|
this.str = str.Substring(1, str.Length - 2); |
|
} else { |
|
int tokenTmp = 1; |
|
/* |
|
* Checking for the following formatting (www.json.org) |
|
* object - {"field1":value,"field2":value} |
|
* array - [value,value,value] |
|
* value - string - "string" |
|
* - number - 0.0 |
|
* - bool - true -or- false |
|
* - null - null |
|
*/ |
|
int offset = 0; |
|
switch(str[offset]) { |
|
case '{': |
|
type = Type.OBJECT; |
|
keys = new List<string>(); |
|
list = new List<JSONObject>(); |
|
break; |
|
case '[': |
|
type = Type.ARRAY; |
|
list = new List<JSONObject>(); |
|
break; |
|
default: |
|
try { |
|
#if USEFLOAT |
|
n = System.Convert.ToSingle(str); |
|
#else |
|
n = System.Convert.ToDouble(str); |
|
#endif |
|
if(!str.Contains(".")) { |
|
i = System.Convert.ToInt64(str); |
|
useInt = true; |
|
} |
|
type = Type.NUMBER; |
|
} catch(System.FormatException) { |
|
type = Type.NULL; |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.LogWarning |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("improper JSON formatting:" + str); |
|
} |
|
return; |
|
} |
|
string propName = ""; |
|
bool openQuote = false; |
|
bool inProp = false; |
|
int depth = 0; |
|
while(++offset < str.Length) { |
|
if(System.Array.IndexOf(WHITESPACE, str[offset]) > -1) |
|
continue; |
|
if(str[offset] == '\\') { |
|
offset += 1; |
|
continue; |
|
} |
|
if(str[offset] == '"') { |
|
if(openQuote) { |
|
if(!inProp && depth == 0 && type == Type.OBJECT) |
|
propName = str.Substring(tokenTmp + 1, offset - tokenTmp - 1); |
|
openQuote = false; |
|
} else { |
|
if(depth == 0 && type == Type.OBJECT) |
|
tokenTmp = offset; |
|
openQuote = true; |
|
} |
|
} |
|
if(openQuote) |
|
continue; |
|
if(type == Type.OBJECT && depth == 0) { |
|
if(str[offset] == ':') { |
|
tokenTmp = offset + 1; |
|
inProp = true; |
|
} |
|
} |
|
|
|
if(str[offset] == '[' || str[offset] == '{') { |
|
depth++; |
|
} else if(str[offset] == ']' || str[offset] == '}') { |
|
depth--; |
|
} |
|
//if (encounter a ',' at top level) || a closing ]/} |
|
if((str[offset] == ',' && depth == 0) || depth < 0) { |
|
inProp = false; |
|
string inner = str.Substring(tokenTmp, offset - tokenTmp).Trim(WHITESPACE); |
|
if(inner.Length > 0) { |
|
if(type == Type.OBJECT) |
|
keys.Add(propName); |
|
if(maxDepth != -1) //maxDepth of -1 is the end of the line |
|
list.Add(Create(inner, (maxDepth < -1) ? -2 : maxDepth - 1)); |
|
else if(storeExcessLevels) |
|
list.Add(CreateBakedObject(inner)); |
|
|
|
} |
|
tokenTmp = offset + 1; |
|
} |
|
} |
|
} |
|
} else type = Type.NULL; |
|
} else type = Type.NULL; //If the string is missing, this is a null |
|
//Profiler.EndSample(); |
|
} |
|
#endregion |
|
public bool IsNumber { get { return type == Type.NUMBER; } } |
|
public bool IsNull { get { return type == Type.NULL; } } |
|
public bool IsString { get { return type == Type.STRING; } } |
|
public bool IsBool { get { return type == Type.BOOL; } } |
|
public bool IsArray { get { return type == Type.ARRAY; } } |
|
public bool IsObject { get { return type == Type.OBJECT || type == Type.BAKED; } } |
|
public void Add(bool val) { |
|
Add(Create(val)); |
|
} |
|
public void Add(float val) { |
|
Add(Create(val)); |
|
} |
|
public void Add(int val) { |
|
Add(Create(val)); |
|
} |
|
public void Add(string str) { |
|
Add(CreateStringObject(str)); |
|
} |
|
public void Add(AddJSONContents content) { |
|
Add(Create(content)); |
|
} |
|
public void Add(JSONObject obj) { |
|
if(obj) { //Don't do anything if the object is null |
|
if(type != Type.ARRAY) { |
|
type = Type.ARRAY; //Congratulations, son, you're an ARRAY now |
|
if(list == null) |
|
list = new List<JSONObject>(); |
|
} |
|
list.Add(obj); |
|
} |
|
} |
|
public void AddField(string name, bool val) { |
|
AddField(name, Create(val)); |
|
} |
|
public void AddField(string name, float val) { |
|
AddField(name, Create(val)); |
|
} |
|
public void AddField(string name, int val) { |
|
AddField(name, Create(val)); |
|
} |
|
public void AddField(string name, long val) { |
|
AddField(name, Create(val)); |
|
} |
|
public void AddField(string name, AddJSONContents content) { |
|
AddField(name, Create(content)); |
|
} |
|
public void AddField(string name, string val) { |
|
AddField(name, CreateStringObject(val)); |
|
} |
|
public void AddField(string name, JSONObject obj) { |
|
if(obj) { //Don't do anything if the object is null |
|
if(type != Type.OBJECT) { |
|
if(keys == null) |
|
keys = new List<string>(); |
|
if(type == Type.ARRAY) { |
|
for(int i = 0; i < list.Count; i++) |
|
keys.Add(i + ""); |
|
} else |
|
if(list == null) |
|
list = new List<JSONObject>(); |
|
type = Type.OBJECT; //Congratulations, son, you're an OBJECT now |
|
} |
|
keys.Add(name); |
|
list.Add(obj); |
|
} |
|
} |
|
public void SetField(string name, string val) { SetField(name, CreateStringObject(val)); } |
|
public void SetField(string name, bool val) { SetField(name, Create(val)); } |
|
public void SetField(string name, float val) { SetField(name, Create(val)); } |
|
public void SetField(string name, int val) { SetField(name, Create(val)); } |
|
public void SetField(string name, JSONObject obj) { |
|
if(HasField(name)) { |
|
list.Remove(this[name]); |
|
keys.Remove(name); |
|
} |
|
AddField(name, obj); |
|
} |
|
public void RemoveField(string name) { |
|
if(keys.IndexOf(name) > -1) { |
|
list.RemoveAt(keys.IndexOf(name)); |
|
keys.Remove(name); |
|
} |
|
} |
|
public delegate void FieldNotFound(string name); |
|
public delegate void GetFieldResponse(JSONObject obj); |
|
public bool GetField(out bool field, string name, bool fallback) { |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
public bool GetField(ref bool field, string name, FieldNotFound fail = null) { |
|
if(type == Type.OBJECT) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = list[index].b; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
#if USEFLOAT |
|
public bool GetField(out float field, string name, float fallback) { |
|
#else |
|
public bool GetField(out double field, string name, double fallback) { |
|
#endif |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
#if USEFLOAT |
|
public bool GetField(ref float field, string name, FieldNotFound fail = null) { |
|
#else |
|
public bool GetField(ref double field, string name, FieldNotFound fail = null) { |
|
#endif |
|
if(type == Type.OBJECT) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = list[index].n; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
public bool GetField(out int field, string name, int fallback) { |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
public bool GetField(ref int field, string name, FieldNotFound fail = null) { |
|
if(IsObject) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = (int)list[index].n; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
public bool GetField(out long field, string name, long fallback) { |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
public bool GetField(ref long field, string name, FieldNotFound fail = null) { |
|
if(IsObject) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = (long)list[index].n; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
public bool GetField(out uint field, string name, uint fallback) { |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
public bool GetField(ref uint field, string name, FieldNotFound fail = null) { |
|
if(IsObject) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = (uint)list[index].n; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
public bool GetField(out string field, string name, string fallback) { |
|
field = fallback; |
|
return GetField(ref field, name); |
|
} |
|
public bool GetField(ref string field, string name, FieldNotFound fail = null) { |
|
if(IsObject) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
field = list[index].str; |
|
return true; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
return false; |
|
} |
|
public void GetField(string name, GetFieldResponse response, FieldNotFound fail = null) { |
|
if(response != null && IsObject) { |
|
int index = keys.IndexOf(name); |
|
if(index >= 0) { |
|
response.Invoke(list[index]); |
|
return; |
|
} |
|
} |
|
if(fail != null) fail.Invoke(name); |
|
} |
|
public JSONObject GetField(string name) { |
|
if(IsObject) |
|
for(int i = 0; i < keys.Count; i++) |
|
if(keys[i] == name) |
|
return list[i]; |
|
return null; |
|
} |
|
public bool HasFields(string[] names) { |
|
if(!IsObject) |
|
return false; |
|
for(int i = 0; i < names.Length; i++) |
|
if(!keys.Contains(names[i])) |
|
return false; |
|
return true; |
|
} |
|
public bool HasField(string name) { |
|
if(!IsObject) |
|
return false; |
|
for(int i = 0; i < keys.Count; i++) |
|
if(keys[i] == name) |
|
return true; |
|
return false; |
|
} |
|
public void Clear() { |
|
type = Type.NULL; |
|
if(list != null) |
|
list.Clear(); |
|
if(keys != null) |
|
keys.Clear(); |
|
str = ""; |
|
n = 0; |
|
b = false; |
|
} |
|
/// <summary> |
|
/// Copy a JSONObject. This could probably work better |
|
/// </summary> |
|
/// <returns></returns> |
|
public JSONObject Copy() { |
|
return Create(Print()); |
|
} |
|
/* |
|
* The Merge function is experimental. Use at your own risk. |
|
*/ |
|
public void Merge(JSONObject obj) { |
|
MergeRecur(this, obj); |
|
} |
|
/// <summary> |
|
/// Merge object right into left recursively |
|
/// </summary> |
|
/// <param name="left">The left (base) object</param> |
|
/// <param name="right">The right (new) object</param> |
|
static void MergeRecur(JSONObject left, JSONObject right) { |
|
if(left.type == Type.NULL) |
|
left.Absorb(right); |
|
else if(left.type == Type.OBJECT && right.type == Type.OBJECT) { |
|
for(int i = 0; i < right.list.Count; i++) { |
|
string key = right.keys[i]; |
|
if(right[i].isContainer) { |
|
if(left.HasField(key)) |
|
MergeRecur(left[key], right[i]); |
|
else |
|
left.AddField(key, right[i]); |
|
} else { |
|
if(left.HasField(key)) |
|
left.SetField(key, right[i]); |
|
else |
|
left.AddField(key, right[i]); |
|
} |
|
} |
|
} else if(left.type == Type.ARRAY && right.type == Type.ARRAY) { |
|
if(right.Count > left.Count) { |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.LogError |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("Cannot merge arrays when right object has more elements"); |
|
return; |
|
} |
|
for(int i = 0; i < right.list.Count; i++) { |
|
if(left[i].type == right[i].type) { //Only overwrite with the same type |
|
if(left[i].isContainer) |
|
MergeRecur(left[i], right[i]); |
|
else { |
|
left[i] = right[i]; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
public void Bake() { |
|
if(type != Type.BAKED) { |
|
str = Print(); |
|
type = Type.BAKED; |
|
} |
|
} |
|
public IEnumerable BakeAsync() { |
|
if(type != Type.BAKED) { |
|
foreach(string s in PrintAsync()) { |
|
if(s == null) |
|
yield return s; |
|
else { |
|
str = s; |
|
} |
|
} |
|
type = Type.BAKED; |
|
} |
|
} |
|
#pragma warning disable 219 |
|
public string Print(bool pretty = false) { |
|
StringBuilder builder = new StringBuilder(); |
|
Stringify(0, builder, pretty); |
|
return builder.ToString(); |
|
} |
|
public IEnumerable<string> PrintAsync(bool pretty = false) { |
|
StringBuilder builder = new StringBuilder(); |
|
printWatch.Reset(); |
|
printWatch.Start(); |
|
foreach(IEnumerable e in StringifyAsync(0, builder, pretty)) { |
|
yield return null; |
|
} |
|
yield return builder.ToString(); |
|
} |
|
#pragma warning restore 219 |
|
#region STRINGIFY |
|
const float maxFrameTime = 0.008f; |
|
static readonly Stopwatch printWatch = new Stopwatch(); |
|
IEnumerable StringifyAsync(int depth, StringBuilder builder, bool pretty = false) { //Convert the JSONObject into a string |
|
//Profiler.BeginSample("JSONprint"); |
|
if(depth++ > MAX_DEPTH) { |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.Log |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("reached max depth!"); |
|
yield break; |
|
} |
|
if(printWatch.Elapsed.TotalSeconds > maxFrameTime) { |
|
printWatch.Reset(); |
|
yield return null; |
|
printWatch.Start(); |
|
} |
|
switch(type) { |
|
case Type.BAKED: |
|
builder.Append(str); |
|
break; |
|
case Type.STRING: |
|
builder.AppendFormat("\"{0}\"", str); |
|
break; |
|
case Type.NUMBER: |
|
if(useInt) { |
|
builder.Append(i.ToString()); |
|
} else { |
|
#if USEFLOAT |
|
if(float.IsInfinity(n)) |
|
builder.Append(INFINITY); |
|
else if(float.IsNegativeInfinity(n)) |
|
builder.Append(NEGINFINITY); |
|
else if(float.IsNaN(n)) |
|
builder.Append(NaN); |
|
#else |
|
if(double.IsInfinity(n)) |
|
builder.Append(INFINITY); |
|
else if(double.IsNegativeInfinity(n)) |
|
builder.Append(NEGINFINITY); |
|
else if(double.IsNaN(n)) |
|
builder.Append(NaN); |
|
#endif |
|
else |
|
builder.Append(n.ToString()); |
|
} |
|
break; |
|
case Type.OBJECT: |
|
builder.Append("{"); |
|
if(list.Count > 0) { |
|
#if(PRETTY) //for a bit more readability, comment the define above to disable system-wide |
|
if(pretty) |
|
builder.Append(NEWLINE); |
|
#endif |
|
for(int i = 0; i < list.Count; i++) { |
|
string key = keys[i]; |
|
JSONObject obj = list[i]; |
|
if(obj) { |
|
#if(PRETTY) |
|
if(pretty) |
|
for(int j = 0; j < depth; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
#endif |
|
builder.AppendFormat("\"{0}\":", key); |
|
foreach(IEnumerable e in obj.StringifyAsync(depth, builder, pretty)) |
|
yield return e; |
|
builder.Append(","); |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append(NEWLINE); |
|
#endif |
|
} |
|
} |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Length -= 2; |
|
else |
|
#endif |
|
builder.Length--; |
|
} |
|
#if(PRETTY) |
|
if(pretty && list.Count > 0) { |
|
builder.Append(NEWLINE); |
|
for(int j = 0; j < depth - 1; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
} |
|
#endif |
|
builder.Append("}"); |
|
break; |
|
case Type.ARRAY: |
|
builder.Append("["); |
|
if(list.Count > 0) { |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append(NEWLINE); //for a bit more readability |
|
#endif |
|
for(int i = 0; i < list.Count; i++) { |
|
if(list[i]) { |
|
#if(PRETTY) |
|
if(pretty) |
|
for(int j = 0; j < depth; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
#endif |
|
foreach(IEnumerable e in list[i].StringifyAsync(depth, builder, pretty)) |
|
yield return e; |
|
builder.Append(","); |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append(NEWLINE); //for a bit more readability |
|
#endif |
|
} |
|
} |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Length -= 2; |
|
else |
|
#endif |
|
builder.Length--; |
|
} |
|
#if(PRETTY) |
|
if(pretty && list.Count > 0) { |
|
builder.Append(NEWLINE); |
|
for(int j = 0; j < depth - 1; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
} |
|
#endif |
|
builder.Append("]"); |
|
break; |
|
case Type.BOOL: |
|
if(b) |
|
builder.Append("true"); |
|
else |
|
builder.Append("false"); |
|
break; |
|
case Type.NULL: |
|
builder.Append("null"); |
|
break; |
|
} |
|
//Profiler.EndSample(); |
|
} |
|
//TODO: Refactor Stringify functions to share core logic |
|
/* |
|
* I know, I know, this is really bad form. It turns out that there is a |
|
* significant amount of garbage created when calling as a coroutine, so this |
|
* method is duplicated. Hopefully there won't be too many future changes, but |
|
* I would still like a more elegant way to optionaly yield |
|
*/ |
|
void Stringify(int depth, StringBuilder builder, bool pretty = false) { //Convert the JSONObject into a string |
|
//Profiler.BeginSample("JSONprint"); |
|
if(depth++ > MAX_DEPTH) { |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.Log |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("reached max depth!"); |
|
return; |
|
} |
|
switch(type) { |
|
case Type.BAKED: |
|
builder.Append(str); |
|
break; |
|
case Type.STRING: |
|
builder.AppendFormat("\"{0}\"", str); |
|
break; |
|
case Type.NUMBER: |
|
if(useInt) { |
|
builder.Append(i.ToString()); |
|
} else { |
|
#if USEFLOAT |
|
if(float.IsInfinity(n)) |
|
builder.Append(INFINITY); |
|
else if(float.IsNegativeInfinity(n)) |
|
builder.Append(NEGINFINITY); |
|
else if(float.IsNaN(n)) |
|
builder.Append(NaN); |
|
#else |
|
if(double.IsInfinity(n)) |
|
builder.Append(INFINITY); |
|
else if(double.IsNegativeInfinity(n)) |
|
builder.Append(NEGINFINITY); |
|
else if(double.IsNaN(n)) |
|
builder.Append(NaN); |
|
#endif |
|
else |
|
builder.Append(n.ToString()); |
|
} |
|
break; |
|
case Type.OBJECT: |
|
builder.Append("{"); |
|
if(list.Count > 0) { |
|
#if(PRETTY) //for a bit more readability, comment the define above to disable system-wide |
|
if(pretty) |
|
builder.Append("\n"); |
|
#endif |
|
for(int i = 0; i < list.Count; i++) { |
|
string key = keys[i]; |
|
JSONObject obj = list[i]; |
|
if(obj) { |
|
#if(PRETTY) |
|
if(pretty) |
|
for(int j = 0; j < depth; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
#endif |
|
builder.AppendFormat("\"{0}\":", key); |
|
obj.Stringify(depth, builder, pretty); |
|
builder.Append(","); |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append("\n"); |
|
#endif |
|
} |
|
} |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Length -= 2; |
|
else |
|
#endif |
|
builder.Length--; |
|
} |
|
#if(PRETTY) |
|
if(pretty && list.Count > 0) { |
|
builder.Append("\n"); |
|
for(int j = 0; j < depth - 1; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
} |
|
#endif |
|
builder.Append("}"); |
|
break; |
|
case Type.ARRAY: |
|
builder.Append("["); |
|
if(list.Count > 0) { |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append("\n"); //for a bit more readability |
|
#endif |
|
for(int i = 0; i < list.Count; i++) { |
|
if(list[i]) { |
|
#if(PRETTY) |
|
if(pretty) |
|
for(int j = 0; j < depth; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
#endif |
|
list[i].Stringify(depth, builder, pretty); |
|
builder.Append(","); |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Append("\n"); //for a bit more readability |
|
#endif |
|
} |
|
} |
|
#if(PRETTY) |
|
if(pretty) |
|
builder.Length -= 2; |
|
else |
|
#endif |
|
builder.Length--; |
|
} |
|
#if(PRETTY) |
|
if(pretty && list.Count > 0) { |
|
builder.Append("\n"); |
|
for(int j = 0; j < depth - 1; j++) |
|
builder.Append("\t"); //for a bit more readability |
|
} |
|
#endif |
|
builder.Append("]"); |
|
break; |
|
case Type.BOOL: |
|
if(b) |
|
builder.Append("true"); |
|
else |
|
builder.Append("false"); |
|
break; |
|
case Type.NULL: |
|
builder.Append("null"); |
|
break; |
|
} |
|
//Profiler.EndSample(); |
|
} |
|
#endregion |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
public static implicit operator WWWForm(JSONObject obj) { |
|
WWWForm form = new WWWForm(); |
|
for(int i = 0; i < obj.list.Count; i++) { |
|
string key = i + ""; |
|
if(obj.type == Type.OBJECT) |
|
key = obj.keys[i]; |
|
string val = obj.list[i].ToString(); |
|
if(obj.list[i].type == Type.STRING) |
|
val = val.Replace("\"", ""); |
|
form.AddField(key, val); |
|
} |
|
return form; |
|
} |
|
#endif |
|
public JSONObject this[int index] { |
|
get { |
|
if(list.Count > index) return list[index]; |
|
return null; |
|
} |
|
set { |
|
if(list.Count > index) |
|
list[index] = value; |
|
} |
|
} |
|
public JSONObject this[string index] { |
|
get { |
|
return GetField(index); |
|
} |
|
set { |
|
SetField(index, value); |
|
} |
|
} |
|
public override string ToString() { |
|
return Print(); |
|
} |
|
public string ToString(bool pretty) { |
|
return Print(pretty); |
|
} |
|
public Dictionary<string, string> ToDictionary() { |
|
if(type == Type.OBJECT) { |
|
Dictionary<string, string> result = new Dictionary<string, string>(); |
|
for(int i = 0; i < list.Count; i++) { |
|
JSONObject val = list[i]; |
|
switch(val.type) { |
|
case Type.STRING: result.Add(keys[i], val.str); break; |
|
case Type.NUMBER: result.Add(keys[i], val.n + ""); break; |
|
case Type.BOOL: result.Add(keys[i], val.b + ""); break; |
|
default: |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.LogWarning |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("Omitting object: " + keys[i] + " in dictionary conversion"); |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
#if UNITY_2 || UNITY_3 || UNITY_4 || UNITY_5 |
|
Debug.Log |
|
#else |
|
Debug.WriteLine |
|
#endif |
|
("Tried to turn non-Object JSONObject into a dictionary"); |
|
return null; |
|
} |
|
public static implicit operator bool(JSONObject o) { |
|
return o != null; |
|
} |
|
#if POOLING |
|
static bool pool = true; |
|
public static void ClearPool() { |
|
pool = false; |
|
releaseQueue.Clear(); |
|
pool = true; |
|
} |
|
|
|
~JSONObject() { |
|
if(pool && releaseQueue.Count < MAX_POOL_SIZE) { |
|
type = Type.NULL; |
|
list = null; |
|
keys = null; |
|
str = ""; |
|
n = 0; |
|
b = false; |
|
releaseQueue.Enqueue(this); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
}
|
|
|