#region Header /** * JsonMapper.cs * JSON to .Net object and object to JSON conversions. * * The authors disclaim copyright to this source code. For more details, see * the COPYING file included with this distribution. **/ #endregion using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using LitJson; namespace XGame.Framework.Json { internal struct PropertyMetadata { public MemberInfo Info; public bool IsField; public Type Type; } internal struct ArrayMetadata { private Type element_type; private bool is_array; private bool is_list; private bool is_k_list; private bool is_k_queue; private bool is_k_stack; public Type ElementType { get { if (element_type == null) return typeof(JsonData); return element_type; } set { element_type = value; } } public bool IsArray { get { return is_array; } set { is_array = value; } } public bool IsList { get { return is_list; } set { is_list = value; } } public bool IsKList { get { return is_k_list; } set { is_k_list = value; } } public bool IsKQueue { get { return is_k_queue; } set { is_k_queue = value; } } public bool IsKStack { get { return is_k_stack; } set { is_k_stack = value; } } } internal struct ObjectMetadata { private Type element_type; private bool is_dictionary; private IDictionary properties; public Type ElementType { get { if (element_type == null) return typeof(JsonData); return element_type; } set { element_type = value; } } public bool IsDictionary { get { return is_dictionary; } set { is_dictionary = value; } } public IDictionary Properties { get { return properties; } set { properties = value; } } } internal delegate void ExporterFunc(object obj, JsonWriter writer); public delegate void ExporterFunc(T obj, JsonWriter writer); internal delegate object ImporterFunc(object input); public delegate TValue ImporterFunc(TJson input); public delegate IJsonWrapper WrapperFactory(); public class XJson { const BindingFlags FLAGS = BindingFlags.Public | BindingFlags.Instance; #region Fields private static int max_nesting_depth; private static IFormatProvider datetime_format; private static IDictionary base_exporters_table; private static IDictionary custom_exporters_table; private static IDictionary> base_importers_table; private static IDictionary> custom_importers_table; private static IDictionary array_metadata; private static readonly object array_metadata_lock = new Object(); private static IDictionary> conv_ops; private static readonly object conv_ops_lock = new Object(); private static IDictionary object_metadata; private static readonly object object_metadata_lock = new Object(); private static IDictionary> type_properties; private static readonly object type_properties_lock = new Object(); private static JsonWriter static_writer; private static readonly object static_writer_lock = new Object(); #endregion #region Constructors static XJson() { max_nesting_depth = 100; array_metadata = new Dictionary(); conv_ops = new Dictionary>(); object_metadata = new Dictionary(); type_properties = new Dictionary>(); static_writer = new JsonWriter(); datetime_format = DateTimeFormatInfo.InvariantInfo; base_exporters_table = new Dictionary(); custom_exporters_table = new Dictionary(); base_importers_table = new Dictionary>(); custom_importers_table = new Dictionary>(); RegisterBaseExporters(); RegisterBaseImporters(); } #endregion internal static void Dispose() { object_metadata.Clear(); array_metadata.Clear(); type_properties.Clear(); foreach (var op in conv_ops) op.Value?.Clear(); conv_ops.Clear(); } #region Private Methods private static void AddArrayMetadata(Type type) { if (array_metadata.ContainsKey(type)) return; ArrayMetadata data = new ArrayMetadata(); data.IsArray = type.IsArray; if (type.GetInterface("System.Collections.IList") != null) data.IsList = true; if (type.GenericTypeArguments.Length == 1) { data.ElementType = type.GenericTypeArguments[0]; } lock (array_metadata_lock) { try { array_metadata.Add(type, data); } catch (ArgumentException) { return; } } } private static void AddObjectMetadata(Type type) { if (object_metadata.ContainsKey(type)) return; ObjectMetadata data = new ObjectMetadata(); if (type.GetInterface("System.Collections.IDictionary") != null) data.IsDictionary = true; data.Properties = new Dictionary(); foreach (PropertyInfo p_info in type.GetProperties(FLAGS)) { if (p_info.Name == "Item") { ParameterInfo[] parameters = p_info.GetIndexParameters(); if (parameters.Length != 1) continue; if (parameters[0].ParameterType == typeof(string)) { data.ElementType = p_info.PropertyType; } continue; } PropertyMetadata p_data = new PropertyMetadata(); p_data.Info = p_info; p_data.Type = p_info.PropertyType; data.Properties.Add(p_info.Name, p_data); } foreach (FieldInfo f_info in type.GetFields(FLAGS)) { PropertyMetadata p_data = new PropertyMetadata(); p_data.Info = f_info; p_data.IsField = true; p_data.Type = f_info.FieldType; data.Properties.Add(f_info.Name, p_data); } lock (object_metadata_lock) { try { object_metadata.Add(type, data); } catch (ArgumentException) { return; } } } private static void AddTypeProperties(Type type) { if (type_properties.ContainsKey(type)) return; IList props = new List(); foreach (PropertyInfo p_info in type.GetProperties(FLAGS)) { if (p_info.Name == "Item") continue; PropertyMetadata p_data = new PropertyMetadata(); p_data.Info = p_info; p_data.IsField = false; props.Add(p_data); } foreach (FieldInfo f_info in type.GetFields(FLAGS)) { PropertyMetadata p_data = new PropertyMetadata(); p_data.Info = f_info; p_data.IsField = true; props.Add(p_data); } lock (type_properties_lock) { try { type_properties.Add(type, props); } catch (ArgumentException) { return; } } } private static MethodInfo GetConvOp(Type t1, Type t2) { lock (conv_ops_lock) { if (!conv_ops.ContainsKey(t1)) conv_ops.Add(t1, new Dictionary()); } if (conv_ops[t1].ContainsKey(t2)) return conv_ops[t1][t2]; MethodInfo op = t1.GetMethod( "op_Implicit", new Type[] { t2 }); lock (conv_ops_lock) { try { conv_ops[t1].Add(t2, op); } catch (ArgumentException) { return conv_ops[t1][t2]; } } return op; } private static object ReadValue(Type inst_type, JsonReader reader) { reader.Read(); if (reader.Token == JsonToken.ArrayEnd) return null; Type underlying_type = Nullable.GetUnderlyingType(inst_type); Type value_type = inst_type; if (reader.Token == JsonToken.Null) { if (inst_type.IsClass || underlying_type != null) { return null; } throw new JsonException(String.Format( "Can't assign null to an instance of type {0}", inst_type)); } if (reader.Token == JsonToken.Double || reader.Token == JsonToken.Int || reader.Token == JsonToken.Long || reader.Token == JsonToken.String || reader.Token == JsonToken.Boolean) { Type json_type = reader.Value.GetType(); var vt = value_type; if (vt.IsAssignableFrom(json_type)) return reader.Value; // If there's a custom importer that fits, use it if (custom_importers_table.ContainsKey(json_type) && custom_importers_table[json_type].ContainsKey( vt)) { ImporterFunc importer = custom_importers_table[json_type][vt]; return importer(reader.Value); } // Maybe there's a base importer that works if (base_importers_table.ContainsKey(json_type) && base_importers_table[json_type].ContainsKey( vt)) { ImporterFunc importer = base_importers_table[json_type][vt]; return importer(reader.Value); } // Maybe it's an enum if (vt.IsEnum) return Enum.ToObject(vt, reader.Value); // Try using an implicit conversion operator MethodInfo conv_op = GetConvOp(vt, json_type); if (conv_op != null) return conv_op.Invoke(null, new object[] { reader.Value }); // No luck throw new JsonException(String.Format( "Can't assign value '{0}' (type {1}) to type {2}", reader.Value, json_type, inst_type)); } object instance = null; if (reader.Token == JsonToken.ArrayStart) { AddArrayMetadata(inst_type); ArrayMetadata t_data = array_metadata[inst_type]; if (!t_data.IsArray && !t_data.IsList && !t_data.IsKList && t_data.IsKQueue && t_data.IsKStack) throw new JsonException(String.Format( "Type {0} can't act as an array", inst_type)); bool isKCollection = t_data.IsKList || t_data.IsKQueue || t_data.IsKStack; object kCollection = null; IList list = null; Type elem_type; if (!t_data.IsArray) { if (isKCollection) { kCollection = Activator.CreateInstance(inst_type); } else { list = (IList)Activator.CreateInstance(inst_type); } elem_type = t_data.ElementType; } else { list = new ArrayList(); elem_type = inst_type.GetElementType(); } if (isKCollection) { { MethodInfo AddMethod = null; if (t_data.IsKQueue) AddMethod = kCollection.GetType().GetMethod("Enqueue", BindingFlags.Public | BindingFlags.Instance); else if (t_data.IsKStack) AddMethod = kCollection.GetType().GetMethod("Push", BindingFlags.Public | BindingFlags.Instance); object[] param = new object[1]; var count = 0; while (true) { object item = ReadValue(elem_type, reader); if (item == null && reader.Token == JsonToken.ArrayEnd) break; param[0] = item; AddMethod.Invoke(kCollection, param); count++; } if (t_data.IsKStack && count > 0) { MethodInfo popMethod = kCollection.GetType().GetMethod("Pop", BindingFlags.Public | BindingFlags.Instance); var tempStack = Activator.CreateInstance(inst_type); object popItem; while (count > 0) { popItem = popMethod.Invoke(kCollection, null); param[0] = popItem; AddMethod.Invoke(tempStack, param); count--; } kCollection = tempStack; } } } else { while (true) { object item = ReadValue(elem_type, reader); if (item == null && reader.Token == JsonToken.ArrayEnd) break; list.Add(item); } } if (t_data.IsArray) { int n = list.Count; var rt = elem_type; instance = Array.CreateInstance(rt, n); for (int i = 0; i < n; i++) ((Array)instance).SetValue(list[i], i); } else { if (isKCollection) instance = kCollection; else instance = list; } } else if (reader.Token == JsonToken.ObjectStart) { AddObjectMetadata(value_type); ObjectMetadata t_data = object_metadata[value_type]; instance = Activator.CreateInstance(value_type); while (true) { reader.Read(); if (reader.Token == JsonToken.ObjectEnd) break; string property = (string)reader.Value; if (t_data.Properties.ContainsKey(property)) { PropertyMetadata prop_data = t_data.Properties[property]; if (prop_data.IsField) { ((FieldInfo)prop_data.Info).SetValue( instance, ReadValue(prop_data.Type, reader)); } else { PropertyInfo p_info = (PropertyInfo)prop_data.Info; if (p_info.CanWrite) p_info.SetValue( instance, ReadValue(prop_data.Type, reader), null); else ReadValue(prop_data.Type, reader); } } else { if (!t_data.IsDictionary) { if (!reader.SkipNonMembers) { throw new JsonException(String.Format( "The type {0} doesn't have the " + "property '{1}'", inst_type, property)); } else { ReadSkip(reader); continue; } } ((IDictionary)instance).Add(property, ReadValue(t_data.ElementType, reader)); } } } return instance; } private static IJsonWrapper ReadValue(WrapperFactory factory, JsonReader reader) { reader.Read(); if (reader.Token == JsonToken.ArrayEnd || reader.Token == JsonToken.Null) return null; IJsonWrapper instance = factory(); if (reader.Token == JsonToken.String) { instance.SetString((string)reader.Value); return instance; } if (reader.Token == JsonToken.Double) { instance.SetDouble((double)reader.Value); return instance; } if (reader.Token == JsonToken.Int) { instance.SetInt((int)reader.Value); return instance; } if (reader.Token == JsonToken.Long) { instance.SetLong((long)reader.Value); return instance; } if (reader.Token == JsonToken.Boolean) { instance.SetBoolean((bool)reader.Value); return instance; } if (reader.Token == JsonToken.ArrayStart) { instance.SetJsonType(JsonType.Array); while (true) { IJsonWrapper item = ReadValue(factory, reader); if (item == null && reader.Token == JsonToken.ArrayEnd) break; ((IList)instance).Add(item); } } else if (reader.Token == JsonToken.ObjectStart) { instance.SetJsonType(JsonType.Object); while (true) { reader.Read(); if (reader.Token == JsonToken.ObjectEnd) break; string property = (string)reader.Value; ((IDictionary)instance)[property] = ReadValue( factory, reader); } } return instance; } private static void ReadSkip(JsonReader reader) { ToWrapper( delegate { return new JsonMockWrapper(); }, reader); } private static void RegisterBaseExporters() { base_exporters_table[typeof(byte)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((byte)obj)); }; base_exporters_table[typeof(char)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToString((char)obj)); }; base_exporters_table[typeof(DateTime)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToString((DateTime)obj, datetime_format)); }; base_exporters_table[typeof(decimal)] = delegate (object obj, JsonWriter writer) { writer.Write((decimal)obj); }; base_exporters_table[typeof(sbyte)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((sbyte)obj)); }; base_exporters_table[typeof(short)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((short)obj)); }; base_exporters_table[typeof(ushort)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((ushort)obj)); }; base_exporters_table[typeof(uint)] = delegate (object obj, JsonWriter writer) { writer.Write(Convert.ToUInt64((uint)obj)); }; base_exporters_table[typeof(ulong)] = delegate (object obj, JsonWriter writer) { writer.Write((ulong)obj); }; base_exporters_table[typeof(UnityEngine.Vector2)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Vector2)obj; writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(v.x); writer.WritePropertyName("y"); writer.Write(v.y); writer.WriteObjectEnd(); }; base_exporters_table[typeof(UnityEngine.Vector3)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Vector3)obj; writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(v.x); writer.WritePropertyName("y"); writer.Write(v.y); writer.WritePropertyName("z"); writer.Write(v.z); writer.WriteObjectEnd(); }; base_exporters_table[typeof(UnityEngine.Vector4)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Vector4)obj; writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(v.x); writer.WritePropertyName("y"); writer.Write(v.y); writer.WritePropertyName("z"); writer.Write(v.z); writer.WritePropertyName("w"); writer.Write(v.z); writer.WriteObjectEnd(); }; base_exporters_table[typeof(UnityEngine.Quaternion)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Quaternion)obj; writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(v.x); writer.WritePropertyName("y"); writer.Write(v.y); writer.WritePropertyName("z"); writer.Write(v.z); writer.WritePropertyName("w"); writer.Write(v.z); writer.WriteObjectEnd(); }; base_exporters_table[typeof(UnityEngine.Color)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Color)obj; writer.WriteObjectStart(); writer.WritePropertyName("r"); writer.Write(v.r); writer.WritePropertyName("g"); writer.Write(v.g); writer.WritePropertyName("b"); writer.Write(v.b); writer.WritePropertyName("a"); writer.Write(v.a); writer.WriteObjectEnd(); }; base_exporters_table[typeof(UnityEngine.Color32)] = delegate (object obj, JsonWriter writer) { var v = (UnityEngine.Color32)obj; writer.WriteObjectStart(); writer.WritePropertyName("r"); writer.Write(v.r); writer.WritePropertyName("g"); writer.Write(v.g); writer.WritePropertyName("b"); writer.Write(v.b); writer.WritePropertyName("a"); writer.Write(v.a); writer.WriteObjectEnd(); }; } private static void RegisterBaseImporters() { ImporterFunc importer; importer = delegate (object input) { return Convert.ToByte((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(byte), importer); importer = delegate (object input) { return Convert.ToUInt64((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(ulong), importer); importer = delegate (object input) { return Convert.ToSByte((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(sbyte), importer); importer = delegate (object input) { return Convert.ToInt16((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(short), importer); importer = delegate (object input) { return Convert.ToInt64((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(long), importer); importer = delegate (object input) { return Convert.ToUInt16((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(ushort), importer); importer = delegate (object input) { return Convert.ToUInt32((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(uint), importer); importer = delegate (object input) { return Convert.ToSingle((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(float), importer); importer = delegate (object input) { return Convert.ToDouble((int)input); }; RegisterImporter(base_importers_table, typeof(int), typeof(double), importer); importer = delegate (object input) { return Convert.ToDecimal((double)input); }; RegisterImporter(base_importers_table, typeof(double), typeof(decimal), importer); importer = delegate (object input) { return Convert.ToSingle((double)input); }; RegisterImporter(base_importers_table, typeof(double), typeof(float), importer); importer = delegate (object input) { return Convert.ToUInt32((long)input); }; RegisterImporter(base_importers_table, typeof(long), typeof(uint), importer); importer = delegate (object input) { return Convert.ToChar((string)input); }; RegisterImporter(base_importers_table, typeof(string), typeof(char), importer); importer = delegate (object input) { return Convert.ToInt32((string)input); }; RegisterImporter(base_importers_table, typeof(string), typeof(int), importer); importer = delegate (object input) { return Convert.ToDateTime((string)input, datetime_format); }; RegisterImporter(base_importers_table, typeof(string), typeof(DateTime), importer); importer = delegate (object input) { return DateTimeOffset.Parse((string)input, datetime_format); }; RegisterImporter(base_importers_table, typeof(string), typeof(DateTimeOffset), importer); } private static void RegisterImporter( IDictionary> table, Type json_type, Type value_type, ImporterFunc importer) { if (!table.ContainsKey(json_type)) table.Add(json_type, new Dictionary()); table[json_type][value_type] = importer; } private static void WriteValue(object obj, JsonWriter writer, bool writer_is_private, int depth) { if (depth > max_nesting_depth) throw new JsonException( String.Format("Max allowed object depth reached while " + "trying to export from type {0}", obj.GetType())); if (obj == null) { writer.Write(null); return; } if (obj is IJsonWrapper jsonwrapper) { if (writer_is_private) writer.WriteJsonWrapper(jsonwrapper); else ((IJsonWrapper)obj).ToJson(writer); return; } if (obj is String) { writer.Write((string)obj); return; } if (obj is Single) { writer.Write((float)obj); return; } if (obj is Double) { writer.Write((double)obj); return; } if (obj is Int32) { writer.Write((int)obj); return; } if (obj is Boolean) { writer.Write((bool)obj); return; } if (obj is Int64) { writer.Write((long)obj); return; } if (obj is Array) { writer.WriteArrayStart(); foreach (object elem in (Array)obj) WriteValue(elem, writer, writer_is_private, depth + 1); writer.WriteArrayEnd(); return; } if (obj is IList) { writer.WriteArrayStart(); foreach (object elem in (IList)obj) WriteValue(elem, writer, writer_is_private, depth + 1); writer.WriteArrayEnd(); return; } if (obj is IDictionary) { writer.WriteObjectStart(); foreach (DictionaryEntry entry in (IDictionary)obj) { writer.WritePropertyName(entry.Key.ToString()); WriteValue(entry.Value, writer, writer_is_private, depth + 1); } writer.WriteObjectEnd(); return; } Type obj_type = obj.GetType(); // See if there's a custom exporter for the object if (custom_exporters_table.ContainsKey(obj_type)) { ExporterFunc exporter = custom_exporters_table[obj_type]; exporter(obj, writer); return; } // If not, maybe there's a base exporter if (base_exporters_table.ContainsKey(obj_type)) { ExporterFunc exporter = base_exporters_table[obj_type]; exporter(obj, writer); return; } // Last option, let's see if it's an enum if (obj is Enum) { Type e_type = Enum.GetUnderlyingType(obj_type); if (e_type == typeof(long) || e_type == typeof(uint) || e_type == typeof(ulong)) writer.Write((ulong)obj); else writer.Write((int)obj); return; } // Okay, so it looks like the input should be exported as an // object AddTypeProperties(obj_type); IList props = type_properties[obj_type]; writer.WriteObjectStart(); foreach (PropertyMetadata p_data in props) { if (p_data.IsField) { writer.WritePropertyName(p_data.Info.Name); WriteValue(((FieldInfo)p_data.Info).GetValue(obj), writer, writer_is_private, depth + 1); } else { PropertyInfo p_info = (PropertyInfo)p_data.Info; if (p_info.CanRead) { writer.WritePropertyName(p_data.Info.Name); WriteValue(p_info.GetValue(obj, null), writer, writer_is_private, depth + 1); } } } writer.WriteObjectEnd(); } #endregion public static string ToJson(object obj) { lock (static_writer_lock) { static_writer.Reset(); WriteValue(obj, static_writer, true, 0); return static_writer.ToString(); } } internal static void ToJson(object obj, JsonWriter writer) { WriteValue(obj, writer, false, 0); } internal static JsonData ToObject(JsonReader reader) { return (JsonData)ToWrapper( delegate { return new JsonData(); }, reader); } internal static JsonData ToObject(TextReader reader) { JsonReader json_reader = new JsonReader(reader); return (JsonData)ToWrapper( delegate { return new JsonData(); }, json_reader); } public static JsonData ToObject(string json) { return (JsonData)ToWrapper( delegate { return new JsonData(); }, json); } internal static T ToObject(JsonReader reader) { return (T)ReadValue(typeof(T), reader); } internal static T ToObject(TextReader reader) { JsonReader json_reader = new JsonReader(reader); return (T)ReadValue(typeof(T), json_reader); } public static T ToObject(string json) { JsonReader reader = new JsonReader(json); return (T)ReadValue(typeof(T), reader); } public static object ToObject(string json, Type type) { JsonReader reader = new JsonReader(json); return ReadValue(type, reader); } internal static IJsonWrapper ToWrapper(WrapperFactory factory, JsonReader reader) { return ReadValue(factory, reader); } internal static IJsonWrapper ToWrapper(WrapperFactory factory, string json) { JsonReader reader = new JsonReader(json); return ReadValue(factory, reader); } internal static void RegisterExporter(ExporterFunc exporter) { ExporterFunc exporter_wrapper = delegate (object obj, JsonWriter writer) { exporter((T)obj, writer); }; custom_exporters_table[typeof(T)] = exporter_wrapper; } internal static void RegisterImporter( ImporterFunc importer) { ImporterFunc importer_wrapper = delegate (object input) { return importer((TJson)input); }; RegisterImporter(custom_importers_table, typeof(TJson), typeof(TValue), importer_wrapper); } internal static void UnregisterExporters() { custom_exporters_table.Clear(); } internal static void UnregisterImporters() { custom_importers_table.Clear(); } } }