375 lines
11 KiB
C#
375 lines
11 KiB
C#
// InternalTools
|
|
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
|
|
namespace Leguar.TotalJSON.Internal {
|
|
|
|
internal static class InternalTools {
|
|
|
|
internal static JValue objectAsJValue(object value) {
|
|
List<object> stack = new List<object>();
|
|
return objectAsJValue(value, stack);
|
|
}
|
|
|
|
private static JValue objectAsJValue(object value, List<object> stack) {
|
|
|
|
// Single object?
|
|
JValue jValue=singleObjectAsJValue(value);
|
|
if (jValue!=null) {
|
|
return jValue;
|
|
}
|
|
|
|
// Dictionary
|
|
if (value is IDictionary) {
|
|
return dictionaryToJSON((IDictionary)(value), new JSON(), stack);
|
|
}
|
|
|
|
// List/array
|
|
if (value is IList) {
|
|
return listToJArray((IList)(value), new JArray(), stack);
|
|
}
|
|
|
|
// Unknown, handled by caller
|
|
return null;
|
|
|
|
}
|
|
|
|
internal static JSON dictionaryToJSON(IDictionary sourceDictionary, JSON targetJSON, List<object> stack) {
|
|
stack.Add(sourceDictionary);
|
|
foreach (object objectKey in sourceDictionary.Keys) {
|
|
if (objectKey==null) { // Dictionary can't have null keys though because calculating hash wouldn't work? No harm to check anyway
|
|
throw (new JArgumentException("Dictionary 'key' can not be null", "sourceDictionary"));
|
|
}
|
|
if (!(objectKey is string)) {
|
|
throw (new JArgumentException("Key have to be string in JSON.<init>(IDictionary) constructor", "sourceDictionary"));
|
|
}
|
|
string stringKey = (string)(objectKey);
|
|
object objectValue = sourceDictionary[objectKey];
|
|
if (stack.Contains(objectValue)) {
|
|
throw (new JArgumentException("Dictionary value is referring to earlier object. This would cause circular JSON.", "sourceDictionary[\""+stringKey+"\"]"));
|
|
}
|
|
JValue jValue = InternalTools.objectAsJValue(objectValue, stack);
|
|
if (jValue==null) {
|
|
throw (new UnknownObjectTypeException(objectValue, "sourceDictionary[\""+stringKey+"\"]"));
|
|
}
|
|
targetJSON.Add(stringKey, jValue);
|
|
}
|
|
stack.Remove(sourceDictionary);
|
|
return targetJSON;
|
|
}
|
|
|
|
internal static JArray listToJArray(IList sourceList, JArray targetJArray, List<object> stack) {
|
|
stack.Add(sourceList);
|
|
for (int n = 0; n<sourceList.Count; n++) {
|
|
object listItem = sourceList[n];
|
|
if (stack.Contains(listItem)) {
|
|
throw (new JArgumentException("List item is referring to earlier object. This would cause circular JSON.", "sourceList["+n+"]"));
|
|
}
|
|
JValue jValue = InternalTools.objectAsJValue(listItem, stack);
|
|
if (jValue==null) {
|
|
throw (new UnknownObjectTypeException(listItem, "sourceList["+n+"]"));
|
|
}
|
|
targetJArray.Add(jValue);
|
|
}
|
|
stack.Remove(sourceList);
|
|
return targetJArray;
|
|
}
|
|
|
|
internal static JValue serializeObject(object obj, SerializeSettings serializeSettings) {
|
|
List<object> stack = new List<object>();
|
|
return serializeObject(obj, serializeSettings, stack);
|
|
}
|
|
|
|
// This never returns null
|
|
private static JValue serializeObject(object obj, SerializeSettings serializeSettings, List<object> stack) {
|
|
|
|
JValue singleValue=singleObjectAsJValue(obj);
|
|
if (singleValue!=null) {
|
|
return singleValue;
|
|
}
|
|
|
|
if (obj is IList) {
|
|
JArray jArray = new JArray();
|
|
IList list = (IList)(obj);
|
|
stack.Add(obj);
|
|
foreach (object listItem in list) {
|
|
if (stack.Contains(listItem)) {
|
|
throw (new SerializeException("List item is referring to earlier object. This would cause circular JSON.", listItem));
|
|
}
|
|
JValue jValue=serializeObject(listItem, serializeSettings, stack);
|
|
if (jValue==null) {
|
|
throw (new SerializeException("List item is type that can't be serialized", listItem));
|
|
}
|
|
jArray.Add(jValue);
|
|
}
|
|
stack.Remove(obj);
|
|
return jArray;
|
|
}
|
|
|
|
Type type = obj.GetType();
|
|
if (type.IsGenericType) {
|
|
if (type.GetGenericTypeDefinition()==typeof(Dictionary<,>)) {
|
|
JSON json = new JSON();
|
|
Type[] dictTypes = type.GetGenericArguments();
|
|
bool dictKeyIsString = (dictTypes[0]==typeof(string));
|
|
if (!serializeSettings.AllowNonStringDictionaryKeys && !dictKeyIsString) {
|
|
throw (new SerializeException("Dictionary key is type ('"+dictTypes[0]+"') that can't be serialized. Dictionary keys must be strings, or allow more loose options using SerializeSettings"));
|
|
}
|
|
IDictionary dict = (IDictionary)(obj);
|
|
stack.Add(obj);
|
|
foreach (object objectKey in dict.Keys) {
|
|
object dictionaryValue = dict[objectKey];
|
|
if (stack.Contains(dictionaryValue)) {
|
|
throw (new SerializeException("Dictionary value is referring to earlier object. This would cause circular JSON.", dictionaryValue));
|
|
}
|
|
JValue jValue = serializeObject(dictionaryValue, serializeSettings, stack);
|
|
if (jValue==null) {
|
|
throw (new SerializeException("Dictionary item is type that can't be serialized", dictionaryValue));
|
|
}
|
|
string stringKey;
|
|
if (dictKeyIsString) {
|
|
stringKey = (string)(objectKey);
|
|
} else {
|
|
stringKey = objectKey.ToString();
|
|
}
|
|
json.Add(stringKey, jValue);
|
|
}
|
|
stack.Remove(obj);
|
|
return json;
|
|
}
|
|
}
|
|
|
|
if (type.IsEnum && serializeSettings.SerializeEnumsDirectlyToInts) {
|
|
FieldInfo[] enumFieldInfos = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
|
|
if (enumFieldInfos.Length == 1) {
|
|
string fieldName = enumFieldInfos[0].Name;
|
|
if (fieldName == "value__") {
|
|
object fieldValue = enumFieldInfos[0].GetValue(obj);
|
|
return new JNumber((int)fieldValue);
|
|
}
|
|
}
|
|
DebugLogger.LogInternalError("InternalTools.serializeObject(): Unexpected enum content");
|
|
}
|
|
|
|
JSON jsonSer = new JSON();
|
|
|
|
FieldInfo[] fieldInfos = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
stack.Add(obj);
|
|
foreach (FieldInfo fieldInfo in fieldInfos) {
|
|
if (isSerializing(fieldInfo, serializeSettings.IgnoreSystemAndUnitySerializeAttributes)) {
|
|
string fieldName = fieldInfo.Name;
|
|
object fieldToSerialize = fieldInfo.GetValue(obj);
|
|
if (stack.Contains(fieldToSerialize)) {
|
|
throw (new SerializeException("Class field is referring to earlier object. This would cause circular JSON.", fieldToSerialize));
|
|
}
|
|
JValue jValue = serializeObject(fieldToSerialize, serializeSettings, stack);
|
|
if (jValue==null) {
|
|
throw (new SerializeException("Field \""+fieldName+"\" is type that can't be serialized",fieldToSerialize));
|
|
}
|
|
jsonSer.Add(fieldName,jValue);
|
|
}
|
|
}
|
|
stack.Remove(obj);
|
|
|
|
return jsonSer;
|
|
|
|
}
|
|
|
|
internal static bool isSerializing(FieldInfo fieldInfo, bool ignoreNonJSONSpecificAttributes) {
|
|
object[] attributes = fieldInfo.GetCustomAttributes(false);
|
|
if (containsType(attributes,typeof(ExcludeFromJSONSerializeAttribute))) {
|
|
return false;
|
|
}
|
|
if (!ignoreNonJSONSpecificAttributes) {
|
|
if (containsType(attributes,typeof(System.NonSerializedAttribute))) {
|
|
return false;
|
|
}
|
|
}
|
|
if (fieldInfo.IsPublic && !fieldInfo.IsLiteral) {
|
|
return true;
|
|
}
|
|
if (containsType(attributes,typeof(IncludeToJSONSerializeAttribute))) {
|
|
return true;
|
|
}
|
|
if (!ignoreNonJSONSpecificAttributes) {
|
|
if (containsType(attributes,typeof(UnityEngine.SerializeField))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool containsType(object[] attributes, Type type) {
|
|
foreach (object attribute in attributes) {
|
|
if (attribute.GetType()==type) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static JValue singleObjectAsJValue(object value) {
|
|
|
|
// Null
|
|
if (value==null) {
|
|
return (new JNull());
|
|
}
|
|
|
|
// JValue directly
|
|
if (value is JValue) {
|
|
return ((JValue)(value));
|
|
}
|
|
|
|
// Known numbers
|
|
if (value is float) {
|
|
return (new JNumber((float)(value)));
|
|
}
|
|
if (value is double) {
|
|
return (new JNumber((double)(value)));
|
|
}
|
|
if (value is decimal) {
|
|
return (new JNumber((decimal)(value)));
|
|
}
|
|
if (value is byte) {
|
|
return (new JNumber((byte)(value)));
|
|
}
|
|
if (value is sbyte) {
|
|
return (new JNumber((sbyte)(value)));
|
|
}
|
|
if (value is short) {
|
|
return (new JNumber((short)(value)));
|
|
}
|
|
if (value is ushort) {
|
|
return (new JNumber((ushort)(value)));
|
|
}
|
|
if (value is int) {
|
|
return (new JNumber((int)(value)));
|
|
}
|
|
if (value is uint) {
|
|
return (new JNumber((uint)(value)));
|
|
}
|
|
if (value is long) {
|
|
return (new JNumber((long)(value)));
|
|
}
|
|
if (value is ulong) {
|
|
return (new JNumber(((ulong)(value)).ToString(CultureInfo.InvariantCulture)));
|
|
}
|
|
|
|
// String
|
|
if (value is string) {
|
|
return (new JString((string)(value)));
|
|
}
|
|
|
|
// Bool
|
|
if (value is bool) {
|
|
return (new JBoolean((bool)(value)));
|
|
}
|
|
|
|
// Unknown, handled by caller
|
|
return null;
|
|
|
|
}
|
|
|
|
internal static object jValueAsSystemObject(JValue jValue) {
|
|
if (jValue is JSON) {
|
|
return ((JSON)(jValue)).AsDictionary();
|
|
} else if (jValue is JArray) {
|
|
return ((JArray)(jValue)).AsList();
|
|
} else if (jValue is JNumber) {
|
|
return ((JNumber)(jValue)).AsObject();
|
|
} else if (jValue is JString) {
|
|
return ((JString)(jValue)).AsString();
|
|
} else if (jValue is JBoolean) {
|
|
return ((JBoolean)(jValue)).AsBool();
|
|
} else { // JNull
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal static string getExceptionMessageTailForID(string debugIDForExceptions, string exceptionSource) {
|
|
if (debugIDForExceptions!=null) {
|
|
return (" - "+exceptionSource+" Debug ID: \""+debugIDForExceptions+"\"");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
internal static string getCleanedStackTrace(string originalStackTrace) {
|
|
string cleanedStackTrace=originalStackTrace;
|
|
bool first=true;
|
|
do {
|
|
int lf=getLineFeedIndex(cleanedStackTrace);
|
|
if (lf<=0) {
|
|
if (first) {
|
|
// This is unexpected, just returning original stacktrace as fallback
|
|
return originalStackTrace;
|
|
} else {
|
|
return cleanedStackTrace;
|
|
}
|
|
}
|
|
first=false;
|
|
if (!isInternalStackTraceLine(cleanedStackTrace.Substring(0,lf))) {
|
|
return cleanedStackTrace;
|
|
}
|
|
cleanedStackTrace=cleanedStackTrace.Substring(lf+1);
|
|
} while (true);
|
|
}
|
|
|
|
private static int getLineFeedIndex(string source) {
|
|
int i=source.IndexOf('\n');
|
|
return i;
|
|
}
|
|
|
|
private static bool isInternalStackTraceLine(string str) {
|
|
const string INTERNAL_STACK_TRACE_PREFIX = "Leguar.TotalJSON.";
|
|
int packageName=str.IndexOf(INTERNAL_STACK_TRACE_PREFIX);
|
|
if (packageName<0) {
|
|
return false;
|
|
}
|
|
int i1=str.IndexOf('(');
|
|
if (i1>0 && i1<packageName) {
|
|
return false;
|
|
}
|
|
int i2=str.IndexOf('<');
|
|
if (i2>0 && i2<packageName) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal static string logSafeCharacter(char chr) {
|
|
if (char.IsControl(chr)) {
|
|
int i = (int)(chr);
|
|
if (i < 256) {
|
|
return ("0x"+i.ToString("X2"));
|
|
}
|
|
return ("0x"+i.ToString("X4"));
|
|
}
|
|
return ("'"+chr+"'");
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
internal static JSONRuntimeDebugContainer getDebugContainer() {
|
|
const string DEBUG_OBJECT_NAME = "TotalJSON_DebugObject";
|
|
UnityEngine.GameObject jsonDebugObject = UnityEngine.GameObject.Find(DEBUG_OBJECT_NAME);
|
|
if (jsonDebugObject==null) {
|
|
jsonDebugObject=new UnityEngine.GameObject(DEBUG_OBJECT_NAME);
|
|
jsonDebugObject.hideFlags=UnityEngine.HideFlags.HideInHierarchy;
|
|
UnityEngine.Object.DontDestroyOnLoad(jsonDebugObject);
|
|
}
|
|
JSONRuntimeDebugContainer jsonRuntimeDebugContainer = jsonDebugObject.GetComponent<JSONRuntimeDebugContainer>();
|
|
if (jsonRuntimeDebugContainer==null) {
|
|
jsonRuntimeDebugContainer=jsonDebugObject.AddComponent<JSONRuntimeDebugContainer>();
|
|
}
|
|
return jsonRuntimeDebugContainer;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|