2025-07-14 21:54:09 +08:00

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
}
}