using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices.ComTypes; using System.Threading; using System.Xml.Linq; using InABox.Clients; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace InABox.Core { public interface ISerializeBinary { public void SerializeBinary(BinaryWriter writer); public void DeserializeBinary(BinaryReader reader); } public static class Serialization { private static JsonSerializerSettings? _serializerSettings; private static JsonSerializerSettings SerializerSettings(bool indented = true) { if (_serializerSettings == null) { _serializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.DateTime, DateFormatHandling = DateFormatHandling.IsoDateFormat, DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind }; _serializerSettings.Converters.Add(new DataTableJsonConverter()); //serializerSettings.Converters.Add(new DateTimeJsonConverter()); _serializerSettings.Converters.Add(new FilterJsonConverter()); _serializerSettings.Converters.Add(new ColumnJsonConverter()); _serializerSettings.Converters.Add(new SortOrderJsonConverter()); _serializerSettings.Converters.Add(new UserPropertiesJsonConverter()); } _serializerSettings.Formatting = indented ? Formatting.Indented : Formatting.None; return _serializerSettings; } public static string Serialize(object? o, bool indented = false) { var json = JsonConvert.SerializeObject(o, SerializerSettings(indented)); return json; } public static void Serialize(object o, Stream stream, bool indented = false) { var settings = SerializerSettings(indented); using (var sw = new StreamWriter(stream)) { using (JsonWriter writer = new JsonTextWriter(sw)) { var serializer = JsonSerializer.Create(settings); serializer.Serialize(writer, o); } } } public static void DeserializeInto(string json, object target) { JsonConvert.PopulateObject(json, target, SerializerSettings()); } [return: MaybeNull] public static T Deserialize(Stream? stream, bool strict = false) { if (stream == null) return default; try { var settings = SerializerSettings(); using var sr = new StreamReader(stream); using JsonReader reader = new JsonTextReader(sr); var serializer = JsonSerializer.Create(settings); return serializer.Deserialize(reader); } catch (Exception e) { if (strict) throw; Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Deserialize<{typeof(T)}>(): {e.Message}"); return default; } } public static object? Deserialize(Type type, Stream? stream) { if (stream == null) return null; object? result = null; var settings = SerializerSettings(); using (var sr = new StreamReader(stream)) { using (JsonReader reader = new JsonTextReader(sr)) { var serializer = JsonSerializer.Create(settings); result = serializer.Deserialize(reader, type); } } return result; } [return: MaybeNull] public static T Deserialize(string? json, bool strict = false) // where T : new() { var ret = default(T); if (string.IsNullOrWhiteSpace(json)) return ret; try { var settings = SerializerSettings(); //if (typeof(T).IsSubclassOf(typeof(BaseObject))) //{ // ret = Activator.CreateInstance(); // (ret as BaseObject).SetObserving(false); // JsonConvert.PopulateObject(json, ret, settings); // (ret as BaseObject).SetObserving(true); //} //else if (typeof(T).IsArray) { object o = Array.CreateInstance(typeof(T).GetElementType(), 0); ret = (T)o; } else { ret = JsonConvert.DeserializeObject(json, settings); } } catch (Exception) { if (strict) { throw; } ret = Activator.CreateInstance(); } return ret; } public static object? Deserialize(Type T, string json) // where T : new() { var ret = T.GetDefault(); if (string.IsNullOrWhiteSpace(json)) return ret; try { var settings = SerializerSettings(); //if (typeof(T).IsSubclassOf(typeof(BaseObject))) //{ // ret = Activator.CreateInstance(); // (ret as BaseObject).SetObserving(false); // JsonConvert.PopulateObject(json, ret, settings); // (ret as BaseObject).SetObserving(true); //} //else if (T.IsArray) { object o = Array.CreateInstance(T.GetElementType(), 0); ret = o; } else { ret = JsonConvert.DeserializeObject(json, T, settings); } } catch (Exception) { ret = Activator.CreateInstance(T); } return ret; } #region Binary Serialization public static byte[] WriteBinary(this ISerializeBinary obj) { using var stream = new MemoryStream(); obj.SerializeBinary(new BinaryWriter(stream)); return stream.ToArray(); } public static T ReadBinary(byte[] data) where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data); public static T ReadBinary(Stream stream) where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream); public static object ReadBinary(Type T, byte[] data) { using var stream = new MemoryStream(data); return ReadBinary(T, stream); } public static object ReadBinary(Type T, Stream stream) { var obj = (Activator.CreateInstance(T) as ISerializeBinary)!; obj.DeserializeBinary(new BinaryReader(stream)); return obj; } #endregion } public static class SerializationUtils { public static void Write(this BinaryWriter writer, Guid guid) { writer.Write(guid.ToByteArray()); } public static Guid ReadGuid(this BinaryReader reader) { return new Guid(reader.ReadBytes(16)); } /// /// Binary serialize a bunch of different types of values. and /// are inverses of each other. /// /// /// Handles [], s of serialisable values, , , , /// , , , , , , , /// , , , , /// and . /// /// /// /// /// If an object of is unable to be serialized. public static void WriteBinaryValue(this BinaryWriter writer, Type type, object? value) { value ??= CoreUtils.GetDefault(type); if (type == typeof(byte[]) && value is byte[] bArray) { writer.Write(bArray.Length); writer.Write(bArray); } else if (type == typeof(byte[]) && value is null) { writer.Write(0); } else if (type.IsArray && value is Array array) { var elementType = type.GetElementType(); writer.Write(array.Length); foreach (var val1 in array) { WriteBinaryValue(writer, elementType, val1); } } else if (type.IsArray && value is null) { writer.Write(0); } else if (type.IsEnum && value is Enum e) { var underlyingType = type.GetEnumUnderlyingType(); WriteBinaryValue(writer, underlyingType, Convert.ChangeType(e, underlyingType)); } else if (type == typeof(bool) && value is bool b) { writer.Write(b); } else if (type == typeof(string) && value is string str) { writer.Write(str); } else if (type == typeof(string) && value is null) { writer.Write(""); } else if (type == typeof(Guid) && value is Guid guid) { writer.Write(guid); } else if (type == typeof(byte) && value is byte i8) { writer.Write(i8); } else if (type == typeof(Int16) && value is Int16 i16) { writer.Write(i16); } else if (type == typeof(Int32) && value is Int32 i32) { writer.Write(i32); } else if (type == typeof(Int64) && value is Int64 i64) { writer.Write(i64); } else if (type == typeof(float) && value is float f32) { writer.Write(f32); } else if (type == typeof(double) && value is double f64) { writer.Write(f64); } else if (type == typeof(DateTime) && value is DateTime date) { writer.Write(date.Ticks); } else if (type == typeof(TimeSpan) && value is TimeSpan time) { writer.Write(time.Ticks); } else if (type == typeof(LoggablePropertyAttribute)) { writer.Write((value as LoggablePropertyAttribute)?.Format ?? ""); } else if (typeof(IPackable).IsAssignableFrom(type) && value is IPackable pack) { pack.Pack(writer); } else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is ISerializeBinary binary) { binary.SerializeBinary(writer); } else if (Nullable.GetUnderlyingType(type) is Type t) { if(value == null) { writer.Write(false); } else { writer.Write(true); writer.WriteBinaryValue(t, value); } } else { throw new Exception($"Invalid type; Target DataType is {type} and value DataType is {value?.GetType().ToString() ?? "null"}"); } } /// /// Binary deserialize a bunch of different types of values. and /// are inverses of each other. /// /// /// Handles [], s of serialisable values, , , , /// , , , , , , , /// , , , , /// and . /// /// /// /// /// If an object of is unable to be deserialized. public static object? ReadBinaryValue(this BinaryReader reader, Type type) { if (type == typeof(byte[])) { var length = reader.ReadInt32(); return reader.ReadBytes(length); } else if (type.IsArray) { var length = reader.ReadInt32(); var elementType = type.GetElementType(); var array = Array.CreateInstance(elementType, length); for (int i = 0; i < array.Length; ++i) { array.SetValue(ReadBinaryValue(reader, elementType), i); } return array; } else if (type.IsEnum) { var val = ReadBinaryValue(reader, type.GetEnumUnderlyingType()); return Enum.ToObject(type, val); } else if (type == typeof(bool)) { return reader.ReadBoolean(); } else if (type == typeof(string)) { return reader.ReadString(); } else if (type == typeof(Guid)) { return reader.ReadGuid(); } else if (type == typeof(byte)) { return reader.ReadByte(); } else if (type == typeof(Int16)) { return reader.ReadInt16(); } else if (type == typeof(Int32)) { return reader.ReadInt32(); } else if (type == typeof(Int64)) { return reader.ReadInt64(); } else if (type == typeof(float)) { return reader.ReadSingle(); } else if (type == typeof(double)) { return reader.ReadDouble(); } else if (type == typeof(DateTime)) { return new DateTime(reader.ReadInt64()); } else if (type == typeof(TimeSpan)) { return new TimeSpan(reader.ReadInt64()); } else if (type == typeof(LoggablePropertyAttribute)) { String format = reader.ReadString(); return String.IsNullOrWhiteSpace(format) ? null : new LoggablePropertyAttribute() { Format = format }; } else if (typeof(IPackable).IsAssignableFrom(type)) { var packable = (Activator.CreateInstance(type) as IPackable)!; packable.Unpack(reader); return packable; } else if (typeof(ISerializeBinary).IsAssignableFrom(type)) { var obj = (Activator.CreateInstance(type) as ISerializeBinary)!; obj.DeserializeBinary(reader); return obj; } else if (Nullable.GetUnderlyingType(type) is Type t) { var isNull = reader.ReadBoolean(); if (isNull) { return null; } else { return reader.ReadBinaryValue(t); } } else { throw new Exception($"Invalid type; Target DataType is {type}"); } } public static IEnumerable SerializableProperties(Type type) => DatabaseSchema.Properties(type) .Where(x => !(x is StandardProperty st) || st.Property.GetCustomAttribute() == null); /// /// An implementation of binary serialising a ; this is the inverse of . /// /// /// Also serialises the names of properties along with the values. /// /// /// /// public static void WriteObject(this BinaryWriter writer, TObject entity) where TObject : BaseObject, new() { var properties = SerializableProperties(typeof(TObject)).ToList(); writer.Write(properties.Count); foreach (var property in properties) { writer.Write(property.Name); writer.WriteBinaryValue(property.PropertyType, property.Getter()(entity)); } } /// /// The inverse of . /// /// /// /// public static TObject ReadObject(this BinaryReader reader) where TObject : BaseObject, new() { var obj = new TObject(); var nProps = reader.ReadInt32(); for(int i = 0; i < nProps; ++i) { var propName = reader.ReadString(); var property = DatabaseSchema.Property(typeof(TObject), propName); property?.Setter()(obj, reader.ReadBinaryValue(property.PropertyType)); } return obj; } /// /// An implementation of binary serialising multiple s; /// this is the inverse of . /// /// /// Also serialises the names of properties along with the values. /// /// /// /// public static void WriteObjects(this BinaryWriter writer, ICollection objects) where TObject : BaseObject, new() { var properties = SerializableProperties(typeof(TObject)).ToList(); writer.Write(objects.Count); writer.Write(properties.Count); foreach (var property in properties) { writer.Write(property.Name); } foreach (var obj in objects) { foreach (var property in properties) { writer.WriteBinaryValue(property.PropertyType, property.Getter()(obj)); } } } /// /// The inverse of . /// /// /// /// public static List ReadObjects(this BinaryReader reader) where TObject : BaseObject, new() { var objs = new List(); var properties = new List(); var nObjs = reader.ReadInt32(); var nProps = reader.ReadInt32(); for(int i = 0; i < nProps; ++i) { var property = reader.ReadString(); properties.Add(DatabaseSchema.Property(typeof(TObject), property)); } for(int i = 0; i < nObjs; ++i) { var obj = new TObject(); foreach(var property in properties) { property?.Setter()(obj, reader.ReadBinaryValue(property.PropertyType)); } objs.Add(obj); } return objs; } } }