Serialization.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.Json;
  8. using System.Text.Json.Serialization;
  9. using System.Text.Json.Serialization.Metadata;
  10. using InABox.Clients;
  11. namespace InABox.Core
  12. {
  13. public enum SerializationFormat
  14. {
  15. Json,
  16. Binary
  17. }
  18. public class SerialisationException : Exception
  19. {
  20. public SerialisationException(string message) : base(message) { }
  21. }
  22. public interface ISerializeBinary
  23. {
  24. public void SerializeBinary(CoreBinaryWriter writer);
  25. public void DeserializeBinary(CoreBinaryReader reader);
  26. }
  27. public static class Serialization
  28. {
  29. /// <summary>
  30. /// TypeInfoResolver modifier that removes properties that don't have setters.
  31. /// </summary>
  32. /// <param name="typeInfo"></param>
  33. public static void WritablePropertiesOnly(JsonTypeInfo typeInfo)
  34. {
  35. if (typeInfo.Kind == JsonTypeInfoKind.Object)
  36. {
  37. var toRemove = typeInfo.Properties.Where(x => x.Set is null).ToList();
  38. foreach (var prop in toRemove)
  39. {
  40. typeInfo.Properties.Remove(prop);
  41. }
  42. }
  43. }
  44. public static List<JsonConverter> DefaultConverters { get; } = new List<JsonConverter>()
  45. {
  46. new CoreTableJsonConverter(),
  47. new FilterJsonConverter(),
  48. new ColumnJsonConverter(),
  49. new ColumnsJsonConverter(),
  50. new SortOrderJsonConverter(),
  51. new UserPropertiesJsonConverter(),
  52. new TypeJsonConverter(),
  53. };
  54. private static JsonSerializerOptions SerializerSettings(bool indented = true, bool populateObject = false)
  55. {
  56. return CreateSerializerSettings(indented, populateObject);
  57. }
  58. public static JsonSerializerOptions CreateSerializerSettings(bool indented = true, bool populateObject = false)
  59. {
  60. var settings = new JsonSerializerOptions { };
  61. foreach (var converter in DefaultConverters)
  62. {
  63. settings.Converters.Add(converter);
  64. }
  65. if (populateObject)
  66. {
  67. settings.TypeInfoResolver = new PopulateTypeInfoResolver(new DefaultJsonTypeInfoResolver());
  68. }
  69. settings.WriteIndented = indented;
  70. return settings;
  71. }
  72. public static string Serialize(object? o, bool indented = false)
  73. {
  74. var json = JsonSerializer.Serialize(o, SerializerSettings(indented));
  75. return json;
  76. }
  77. public static void Serialize(object o, Stream stream, bool indented = false)
  78. {
  79. var settings = SerializerSettings(indented);
  80. JsonSerializer.Serialize(stream, o, settings);
  81. }
  82. [return: MaybeNull]
  83. public static T Deserialize<T>(Stream? stream, bool strict = false)
  84. {
  85. if (stream == null)
  86. return default;
  87. try
  88. {
  89. var settings = SerializerSettings();
  90. return JsonSerializer.Deserialize<T>(stream, settings);
  91. }
  92. catch (Exception e)
  93. {
  94. if (strict)
  95. throw;
  96. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Deserialize<{typeof(T)}>(): {e.Message}");
  97. return default;
  98. }
  99. }
  100. public static object? Deserialize(Type type, Stream? stream)
  101. {
  102. if (stream == null)
  103. return null;
  104. object? result = null;
  105. var settings = SerializerSettings();
  106. result = JsonSerializer.Deserialize(stream, type, settings);
  107. return result;
  108. }
  109. [return: MaybeNull]
  110. public static T Deserialize<T>(string? json, bool strict = false) // where T : new()
  111. {
  112. var ret = default(T);
  113. if (string.IsNullOrWhiteSpace(json))
  114. return ret;
  115. try
  116. {
  117. var settings = SerializerSettings();
  118. if (typeof(T).IsArray)
  119. {
  120. ret = JsonSerializer.Deserialize<T>(json, settings);
  121. }
  122. else
  123. {
  124. ret = JsonSerializer.Deserialize<T>(json, settings);
  125. }
  126. }
  127. catch (Exception e)
  128. {
  129. if (strict)
  130. {
  131. throw;
  132. }
  133. CoreUtils.LogException("", e);
  134. if (typeof(T).IsArray)
  135. {
  136. ret = (T)(object)Array.CreateInstance(typeof(T).GetElementType(), 0);
  137. }
  138. else
  139. {
  140. ret = (T)Activator.CreateInstance(typeof(T), true);
  141. }
  142. }
  143. return ret;
  144. }
  145. [return: MaybeNull]
  146. public static void DeserializeInto<T>(string? json, T obj, bool strict = false)
  147. {
  148. if (string.IsNullOrWhiteSpace(json))
  149. return;
  150. try
  151. {
  152. var settings = SerializerSettings(populateObject: true);
  153. PopulateTypeInfoResolver.t_populateObject = obj;
  154. if (typeof(T).IsArray)
  155. {
  156. JsonSerializer.Deserialize<T>(json, settings);
  157. }
  158. else
  159. {
  160. JsonSerializer.Deserialize<T>(json, settings);
  161. }
  162. }
  163. catch (Exception e)
  164. {
  165. if (strict)
  166. {
  167. throw;
  168. }
  169. CoreUtils.LogException("", e);
  170. }
  171. finally
  172. {
  173. PopulateTypeInfoResolver.t_populateObject = null;
  174. }
  175. }
  176. public static object? Deserialize(Type T, string json) // where T : new()
  177. {
  178. var ret = T.GetDefault();
  179. if (string.IsNullOrWhiteSpace(json))
  180. return ret;
  181. try
  182. {
  183. var settings = SerializerSettings();
  184. if (T.IsArray)
  185. {
  186. object o = Array.CreateInstance(T.GetElementType(), 0);
  187. ret = o;
  188. }
  189. else
  190. {
  191. ret = JsonSerializer.Deserialize(json, T, settings);
  192. }
  193. }
  194. catch (Exception)
  195. {
  196. ret = Activator.CreateInstance(T, true);
  197. }
  198. return ret;
  199. }
  200. #region Binary Serialization
  201. public static byte[] WriteBinary(this ISerializeBinary obj, BinarySerializationSettings settings)
  202. {
  203. using var stream = new MemoryStream();
  204. obj.SerializeBinary(new CoreBinaryWriter(stream, settings));
  205. return stream.ToArray();
  206. }
  207. public static void WriteBinary(this ISerializeBinary obj, Stream stream, BinarySerializationSettings settings)
  208. {
  209. obj.SerializeBinary(new CoreBinaryWriter(stream, settings));
  210. }
  211. public static T ReadBinary<T>(byte[] data, BinarySerializationSettings settings)
  212. where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data, settings);
  213. public static T ReadBinary<T>(Stream stream, BinarySerializationSettings settings)
  214. where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream, settings);
  215. public static object ReadBinary(Type T, byte[] data, BinarySerializationSettings settings)
  216. {
  217. using var stream = new MemoryStream(data);
  218. return ReadBinary(T, stream, settings);
  219. }
  220. public static object ReadBinary(Type T, Stream stream, BinarySerializationSettings settings)
  221. {
  222. var obj = (Activator.CreateInstance(T) as ISerializeBinary)!;
  223. obj.DeserializeBinary(new CoreBinaryReader(stream, settings));
  224. return obj;
  225. }
  226. #endregion
  227. }
  228. internal class PopulateTypeInfoResolver : IJsonTypeInfoResolver
  229. {
  230. private readonly IJsonTypeInfoResolver? _jsonTypeInfoResolver;
  231. [ThreadStatic]
  232. internal static object? t_populateObject;
  233. public PopulateTypeInfoResolver(IJsonTypeInfoResolver? jsonTypeInfoResolver)
  234. {
  235. _jsonTypeInfoResolver = jsonTypeInfoResolver;
  236. }
  237. public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
  238. {
  239. var typeInfo = _jsonTypeInfoResolver?.GetTypeInfo(type, options);
  240. if (typeInfo != null && typeInfo.Kind != JsonTypeInfoKind.None)
  241. {
  242. var defaultCreateObject = typeInfo.CreateObject;
  243. if (defaultCreateObject != null)
  244. {
  245. typeInfo.CreateObject = () =>
  246. {
  247. if (t_populateObject != null)
  248. {
  249. var result = t_populateObject;
  250. t_populateObject = null;
  251. return result;
  252. }
  253. else
  254. {
  255. return defaultCreateObject.Invoke();
  256. }
  257. };
  258. }
  259. }
  260. return typeInfo;
  261. }
  262. }
  263. public class CoreBinaryReader : BinaryReader
  264. {
  265. public BinarySerializationSettings Settings { get; set; }
  266. public CoreBinaryReader(Stream stream, BinarySerializationSettings settings) : base(stream)
  267. {
  268. Settings = settings;
  269. }
  270. }
  271. public class CoreBinaryWriter : BinaryWriter
  272. {
  273. public BinarySerializationSettings Settings { get; set; }
  274. public CoreBinaryWriter(Stream stream, BinarySerializationSettings settings) : base(stream)
  275. {
  276. Settings = settings;
  277. }
  278. }
  279. /// <summary>
  280. /// A class to maintain the consistency of serialisation formats across versions.
  281. /// The design of this is such that specific versions of serialisation have different parameters set,
  282. /// and the versions are maintained as static properties. Please keep the constructor private.
  283. /// </summary>
  284. /// <remarks>
  285. /// Note that <see cref="Latest"/> should always be updated to point to the latest version.
  286. /// <br/>
  287. /// Note also that all versions should have an entry in the <see cref="ConvertVersionString(string)"/> function.
  288. /// <br/>
  289. /// Also, if you create a new format, it would probably be a good idea to add a database update script to get all
  290. /// <see cref="IPackable"/> and <see cref="ISerializeBinary"/> properties and update the version of the format.
  291. /// (Otherwise, we'd basically be nullifying all data that is currently binary serialised.)
  292. /// </remarks>
  293. public class BinarySerializationSettings
  294. {
  295. /// <summary>
  296. /// Should the Info() call return RPC and Rest Ports? This is
  297. /// To workaround a bug in RPCsockets that crash on large uploads
  298. /// </summary>
  299. /// <remarks>
  300. /// True in all serialization versions >= 1.2
  301. /// </remarks>
  302. public bool RPCClientWorkaround { get; set; }
  303. /// <summary>
  304. /// Should reference types include a flag for nullability? (Adds an extra boolean field for whether the value is null or not).
  305. /// </summary>
  306. /// <remarks>
  307. /// True in all serialisation versions >= 1.1.
  308. /// </remarks>
  309. public bool IncludeNullables { get; set; }
  310. public string Version { get; set; }
  311. public static BinarySerializationSettings Latest => V1_2;
  312. public static BinarySerializationSettings V1_0 = new BinarySerializationSettings("1.0")
  313. {
  314. IncludeNullables = false,
  315. RPCClientWorkaround = false
  316. };
  317. public static BinarySerializationSettings V1_1 = new BinarySerializationSettings("1.1")
  318. {
  319. IncludeNullables = true,
  320. RPCClientWorkaround = false
  321. };
  322. public static BinarySerializationSettings V1_2 = new BinarySerializationSettings("1.2")
  323. {
  324. IncludeNullables = true,
  325. RPCClientWorkaround = true
  326. };
  327. public static BinarySerializationSettings ConvertVersionString(string version) => version switch
  328. {
  329. "1.0" => V1_0,
  330. "1.1" => V1_1,
  331. "1.2" => V1_2,
  332. _ => V1_0
  333. };
  334. private BinarySerializationSettings(string version)
  335. {
  336. Version = version;
  337. }
  338. }
  339. public static class SerializationUtils
  340. {
  341. public static void Write(this BinaryWriter writer, Guid guid)
  342. {
  343. writer.Write(guid.ToByteArray());
  344. }
  345. public static Guid ReadGuid(this BinaryReader reader)
  346. {
  347. return new Guid(reader.ReadBytes(16));
  348. }
  349. public static void Write(this BinaryWriter writer, DateTime dateTime)
  350. {
  351. writer.Write(dateTime.Ticks);
  352. }
  353. public static DateTime ReadDateTime(this BinaryReader reader)
  354. {
  355. return new DateTime(reader.ReadInt64());
  356. }
  357. private static bool MatchType<T1>(Type t) => typeof(T1) == t;
  358. private static bool MatchType<T1, T2>(Type t) => (typeof(T1) == t) || (typeof(T2) == t);
  359. /// <summary>
  360. /// Binary serialize a bunch of different types of values. <see cref="WriteBinaryValue(CoreBinaryWriter, Type, object?)"/> and
  361. /// <see cref="ReadBinaryValue(CoreBinaryReader, Type)"/> are inverses of each other.
  362. /// </summary>
  363. /// <remarks>
  364. /// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
  365. /// <see cref="Guid"/>, <see cref="byte"/>, <see cref="Int16"/>, <see cref="Int32"/>, <see cref="Int64"/>, <see cref="float"/>, <see cref="double"/>,
  366. /// <see cref="DateTime"/>, <see cref="TimeSpan"/>, <see cref="LoggablePropertyAttribute"/>, <see cref="IPackable"/>, <see cref="Nullable{T}"/>
  367. /// and <see cref="ISerializeBinary"/>.
  368. /// </remarks>
  369. /// <param name="writer"></param>
  370. /// <param name="type"></param>
  371. /// <param name="value"></param>
  372. /// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be serialized.</exception>
  373. public static void WriteBinaryValue(this CoreBinaryWriter writer, Type type, object? value)
  374. {
  375. value ??= CoreUtils.GetDefault(type);
  376. if (value == null)
  377. {
  378. if (MatchType<string>(type))
  379. writer.Write("");
  380. else if (writer.Settings.IncludeNullables && typeof(IPackable).IsAssignableFrom(type))
  381. writer.Write(false);
  382. else if (writer.Settings.IncludeNullables && typeof(ISerializeBinary).IsAssignableFrom(type))
  383. writer.Write(false);
  384. else if (Nullable.GetUnderlyingType(type) is Type t)
  385. writer.Write(false);
  386. else if (MatchType<LoggablePropertyAttribute, object>(type))
  387. writer.Write("");
  388. else
  389. writer.Write(0);
  390. }
  391. else if (MatchType<byte[], object>(type) && value is byte[] bArray)
  392. {
  393. writer.Write(bArray.Length);
  394. writer.Write(bArray);
  395. }
  396. else if (type.IsArray && value is Array array)
  397. {
  398. var elementType = type.GetElementType();
  399. writer.Write(array.Length);
  400. foreach (var val1 in array)
  401. {
  402. WriteBinaryValue(writer, elementType, val1);
  403. }
  404. }
  405. else if (type.IsEnum && value is Enum e)
  406. {
  407. var underlyingType = type.GetEnumUnderlyingType();
  408. WriteBinaryValue(writer, underlyingType, Convert.ChangeType(e, underlyingType));
  409. }
  410. else if (MatchType<bool, object>(type) && value is bool b)
  411. {
  412. writer.Write(b);
  413. }
  414. else if (MatchType<string, object>(type) && value is string str)
  415. writer.Write(str);
  416. else if (MatchType<Guid, object>(type) && value is Guid guid)
  417. writer.Write(guid);
  418. else if (MatchType<byte, object>(type) && value is byte i8)
  419. writer.Write(i8);
  420. else if (MatchType<Int16, object>(type) && value is Int16 i16)
  421. writer.Write(i16);
  422. else if (MatchType<Int32, object>(type) && value is Int32 i32)
  423. writer.Write(i32);
  424. else if (MatchType<Int64, object>(type) && value is Int64 i64)
  425. writer.Write(i64);
  426. else if (MatchType<float, object>(type) && value is float f32)
  427. writer.Write(f32);
  428. else if (MatchType<double, object>(type) && value is double f64)
  429. writer.Write(f64);
  430. else if (MatchType<DateTime, object>(type) && value is DateTime date)
  431. writer.Write(date.Ticks);
  432. else if (MatchType<TimeSpan, object>(type) && value is TimeSpan time)
  433. writer.Write(time.Ticks);
  434. else if (MatchType<LoggablePropertyAttribute, object>(type) && value is LoggablePropertyAttribute lpa)
  435. writer.Write(lpa.Format ?? string.Empty);
  436. else if (typeof(IPackable).IsAssignableFrom(type) && value is IPackable pack)
  437. {
  438. if (writer.Settings.IncludeNullables)
  439. writer.Write(true);
  440. pack.Pack(writer);
  441. }
  442. else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is ISerializeBinary binary)
  443. {
  444. if (writer.Settings.IncludeNullables)
  445. writer.Write(true);
  446. binary.SerializeBinary(writer);
  447. }
  448. else if (Nullable.GetUnderlyingType(type) is Type t)
  449. {
  450. writer.Write(true);
  451. writer.WriteBinaryValue(t, value);
  452. }
  453. else if (value is UserProperty userprop)
  454. WriteBinaryValue(writer, userprop.Type, userprop.Value);
  455. else
  456. throw new SerialisationException($"Invalid type; Target DataType is {type} and value DataType is {value?.GetType().ToString() ?? "null"}");
  457. }
  458. public static void WriteBinaryValue<T>(this CoreBinaryWriter writer, T value)
  459. => WriteBinaryValue(writer, typeof(T), value);
  460. /// <summary>
  461. /// Binary deserialize a bunch of different types of values. <see cref="WriteBinaryValue(CoreBinaryWriter, Type, object?)"/> and
  462. /// <see cref="ReadBinaryValue(CoreBinaryReader, Type)"/> are inverses of each other.
  463. /// </summary>
  464. /// <remarks>
  465. /// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
  466. /// <see cref="Guid"/>, <see cref="byte"/>, <see cref="Int16"/>, <see cref="Int32"/>, <see cref="Int64"/>, <see cref="float"/>, <see cref="double"/>,
  467. /// <see cref="DateTime"/>, <see cref="TimeSpan"/>, <see cref="LoggablePropertyAttribute"/>, <see cref="IPackable"/>, <see cref="Nullable{T}"/>
  468. /// and <see cref="ISerializeBinary"/>.
  469. /// </remarks>
  470. /// <param name="reader"></param>
  471. /// <param name="type"></param>
  472. /// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be deserialized.</exception>
  473. public static object? ReadBinaryValue(this CoreBinaryReader reader, Type type)
  474. {
  475. if (type == typeof(byte[]))
  476. {
  477. var length = reader.ReadInt32();
  478. return reader.ReadBytes(length);
  479. }
  480. else if (type.IsArray)
  481. {
  482. var length = reader.ReadInt32();
  483. var elementType = type.GetElementType();
  484. var array = Array.CreateInstance(elementType, length);
  485. for (int i = 0; i < array.Length; ++i)
  486. {
  487. array.SetValue(ReadBinaryValue(reader, elementType), i);
  488. }
  489. return array;
  490. }
  491. else if (type.IsEnum)
  492. {
  493. var val = ReadBinaryValue(reader, type.GetEnumUnderlyingType());
  494. return Enum.ToObject(type, val);
  495. }
  496. else if (type == typeof(bool))
  497. {
  498. return reader.ReadBoolean();
  499. }
  500. else if (type == typeof(string))
  501. {
  502. return reader.ReadString();
  503. }
  504. else if (type == typeof(Guid))
  505. {
  506. return reader.ReadGuid();
  507. }
  508. else if (type == typeof(byte))
  509. {
  510. return reader.ReadByte();
  511. }
  512. else if (type == typeof(Int16))
  513. {
  514. return reader.ReadInt16();
  515. }
  516. else if (type == typeof(Int32))
  517. {
  518. return reader.ReadInt32();
  519. }
  520. else if (type == typeof(Int64))
  521. {
  522. return reader.ReadInt64();
  523. }
  524. else if (type == typeof(float))
  525. {
  526. return reader.ReadSingle();
  527. }
  528. else if (type == typeof(double))
  529. {
  530. return reader.ReadDouble();
  531. }
  532. else if (type == typeof(DateTime))
  533. {
  534. return new DateTime(reader.ReadInt64());
  535. }
  536. else if (type == typeof(TimeSpan))
  537. {
  538. return new TimeSpan(reader.ReadInt64());
  539. }
  540. else if (type == typeof(LoggablePropertyAttribute))
  541. {
  542. String format = reader.ReadString();
  543. return String.IsNullOrWhiteSpace(format)
  544. ? null
  545. : new LoggablePropertyAttribute() { Format = format };
  546. }
  547. else if (typeof(IPackable).IsAssignableFrom(type))
  548. {
  549. if (!reader.Settings.IncludeNullables || reader.ReadBoolean()) // Note the short-circuit operator preventing reading a boolean.
  550. {
  551. var packable = (Activator.CreateInstance(type) as IPackable)!;
  552. packable.Unpack(reader);
  553. return packable;
  554. }
  555. else
  556. {
  557. return null;
  558. }
  559. }
  560. else if (typeof(ISerializeBinary).IsAssignableFrom(type))
  561. {
  562. if (!reader.Settings.IncludeNullables || reader.ReadBoolean()) // Note the short-circuit operator preventing reading a boolean.
  563. {
  564. var obj = (Activator.CreateInstance(type, true) as ISerializeBinary)!;
  565. obj.DeserializeBinary(reader);
  566. return obj;
  567. }
  568. else
  569. {
  570. return null;
  571. }
  572. }
  573. else if (Nullable.GetUnderlyingType(type) is Type t)
  574. {
  575. var isNull = reader.ReadBoolean();
  576. if (isNull)
  577. {
  578. return null;
  579. }
  580. else
  581. {
  582. return reader.ReadBinaryValue(t);
  583. }
  584. }
  585. else
  586. {
  587. throw new SerialisationException($"Invalid type; Target DataType is {type}");
  588. }
  589. }
  590. public static T ReadBinaryValue<T>(this CoreBinaryReader reader)
  591. {
  592. var result = ReadBinaryValue(reader, typeof(T));
  593. return (result != null ? (T)result : default)!;
  594. }
  595. public static IEnumerable<IProperty> SerializableProperties(Type type, Predicate<IProperty>? filter = null) =>
  596. DatabaseSchema.Properties(type)
  597. .Where(x => (!(x is StandardProperty st) || st.IsSerializable) && (filter?.Invoke(x) ?? true));
  598. private static void WriteOriginalValues<TObject>(this CoreBinaryWriter writer, TObject obj)
  599. where TObject : BaseObject
  600. {
  601. var originalValues = new List<Tuple<Type, string, object?>>();
  602. foreach (var (key, value) in obj.OriginalValueList)
  603. {
  604. if (DatabaseSchema.Property(obj.GetType(), key) is IProperty prop && prop.IsSerializable)
  605. {
  606. originalValues.Add(new Tuple<Type, string, object?>(prop.PropertyType, key, value));
  607. }
  608. }
  609. writer.Write(originalValues.Count);
  610. foreach (var (type, key, value) in originalValues)
  611. {
  612. writer.Write(key);
  613. try
  614. {
  615. writer.WriteBinaryValue(type, value);
  616. }
  617. catch (Exception e)
  618. {
  619. CoreUtils.LogException("", e, "Error serialising OriginalValues");
  620. }
  621. }
  622. }
  623. private static void ReadOriginalValues<TObject>(this CoreBinaryReader reader, TObject obj)
  624. where TObject : BaseObject
  625. {
  626. var nOriginalValues = reader.ReadInt32();
  627. for (int i = 0; i < nOriginalValues; ++i)
  628. {
  629. var key = reader.ReadString();
  630. if (DatabaseSchema.Property(obj.GetType(), key) is IProperty prop)
  631. {
  632. var value = reader.ReadBinaryValue(prop.PropertyType);
  633. obj.OriginalValueList[prop.Name] = value;
  634. }
  635. }
  636. }
  637. public static void WriteObject<TObject>(this CoreBinaryWriter writer, TObject entity, Type type)
  638. where TObject : BaseObject
  639. {
  640. if (!typeof(TObject).IsAssignableFrom(type))
  641. throw new SerialisationException($"{type.EntityName()} is not a subclass of {typeof(TObject).EntityName()}");
  642. var properties = SerializableProperties(type).ToList();
  643. writer.Write(properties.Count);
  644. foreach (var property in properties)
  645. {
  646. writer.Write(property.Name);
  647. writer.WriteBinaryValue(property.PropertyType, property.Getter()(entity));
  648. }
  649. writer.WriteOriginalValues(entity);
  650. }
  651. /// <summary>
  652. /// An implementation of binary serialising a <typeparamref name="TObject"/>; this is the inverse of <see cref="ReadObject{TObject}(CoreBinaryReader)"/>.
  653. /// </summary>
  654. /// <remarks>
  655. /// Also serialises the names of properties along with the values.
  656. /// </remarks>
  657. /// <typeparam name="TObject"></typeparam>
  658. /// <param name="writer"></param>
  659. /// <param name="entity"></param>
  660. public static void WriteObject<TObject>(this CoreBinaryWriter writer, TObject entity)
  661. where TObject : BaseObject, new() => WriteObject(writer, entity, typeof(TObject));
  662. public static TObject ReadObject<TObject>(this CoreBinaryReader reader, Type type)
  663. where TObject : BaseObject
  664. {
  665. if (!typeof(TObject).IsAssignableFrom(type))
  666. throw new SerialisationException($"{type.EntityName()} is not a subclass of {typeof(TObject).EntityName()}");
  667. var obj = (Activator.CreateInstance(type) as TObject)!;
  668. obj.SetObserving(false);
  669. var nProps = reader.ReadInt32();
  670. for (int i = 0; i < nProps; ++i)
  671. {
  672. var propName = reader.ReadString();
  673. var property = DatabaseSchema.Property(type, propName)
  674. ?? throw new SerialisationException($"Property {propName} does not exist on {type.EntityName()}");
  675. property.Setter()(obj, reader.ReadBinaryValue(property.PropertyType));
  676. }
  677. reader.ReadOriginalValues(obj);
  678. obj.SetObserving(true);
  679. return obj;
  680. }
  681. /// <summary>
  682. /// The inverse of <see cref="WriteObject{TObject}(CoreBinaryWriter, TObject)"/>.
  683. /// </summary>
  684. /// <typeparam name="TObject"></typeparam>
  685. /// <param name="reader"></param>
  686. /// <returns></returns>
  687. public static TObject ReadObject<TObject>(this CoreBinaryReader reader)
  688. where TObject : BaseObject, new() => reader.ReadObject<TObject>(typeof(TObject));
  689. /// <summary>
  690. /// An implementation of binary serialising multiple <typeparamref name="TObject"/>s;
  691. /// this is the inverse of <see cref="ReadObjects{TObject}(CoreBinaryReader)"/>.
  692. /// </summary>
  693. /// <remarks>
  694. /// Also serialises the names of properties along with the values.
  695. /// </remarks>
  696. /// <typeparam name="TObject"></typeparam>
  697. /// <param name="writer"></param>
  698. /// <param name="objects"></param>
  699. public static void WriteObjects<TObject>(this CoreBinaryWriter writer, ICollection<TObject>? objects)
  700. where TObject : BaseObject, new() => WriteObjects(writer, typeof(TObject), objects);
  701. public static void WriteObjects<TObject>(this CoreBinaryWriter writer, Type type, ICollection<TObject>? objects, Predicate<IProperty>? filter = null)
  702. where TObject : BaseObject
  703. {
  704. if (!typeof(TObject).IsAssignableFrom(type))
  705. throw new SerialisationException($"{type.EntityName()} is not a subclass of {typeof(TObject).EntityName()}");
  706. var nObjs = objects?.Count ?? 0;
  707. writer.Write(nObjs);
  708. if (nObjs == 0)
  709. {
  710. return;
  711. }
  712. var properties = SerializableProperties(type, filter).ToList();
  713. writer.Write(properties.Count);
  714. foreach (var property in properties)
  715. {
  716. writer.Write(property.Name);
  717. }
  718. if (objects != null)
  719. {
  720. foreach (var obj in objects)
  721. {
  722. foreach (var property in properties)
  723. {
  724. writer.WriteBinaryValue(property.PropertyType, property.Getter()(obj));
  725. }
  726. writer.WriteOriginalValues(obj);
  727. }
  728. }
  729. }
  730. /// <summary>
  731. /// The inverse of <see cref="WriteObjects{TObject}(CoreBinaryWriter, ICollection{TObject})"/>.
  732. /// </summary>
  733. /// <typeparam name="TObject"></typeparam>
  734. /// <param name="reader"></param>
  735. /// <returns></returns>
  736. public static List<TObject> ReadObjects<TObject>(this CoreBinaryReader reader)
  737. where TObject : BaseObject, new()
  738. {
  739. return ReadObjects<TObject>(reader, typeof(TObject));
  740. }
  741. public static List<TObject> ReadObjects<TObject>(this CoreBinaryReader reader, Type type) where TObject : BaseObject
  742. {
  743. if (!typeof(TObject).IsAssignableFrom(type))
  744. throw new SerialisationException($"{type.EntityName()} is not a subclass of {typeof(TObject).EntityName()}");
  745. var objs = new List<TObject>();
  746. var properties = new List<IProperty>();
  747. var nObjs = reader.ReadInt32();
  748. if (nObjs == 0)
  749. {
  750. return objs;
  751. }
  752. var nProps = reader.ReadInt32();
  753. for (int i = 0; i < nProps; ++i)
  754. {
  755. var propertyName = reader.ReadString();
  756. var property = DatabaseSchema.Property(type, propertyName)
  757. ?? throw new SerialisationException($"Property {propertyName} does not exist on {type.EntityName()}");
  758. properties.Add(property);
  759. }
  760. for (int i = 0; i < nObjs; ++i)
  761. {
  762. var obj = (Activator.CreateInstance(type) as TObject)!;
  763. obj.SetObserving(false);
  764. foreach (var property in properties)
  765. {
  766. property.Setter()(obj, reader.ReadBinaryValue(property.PropertyType));
  767. }
  768. reader.ReadOriginalValues(obj);
  769. obj.SetObserving(true);
  770. objs.Add(obj);
  771. }
  772. return objs;
  773. }
  774. }
  775. public interface IPolymorphicallySerialisable { }
  776. /// <summary>
  777. /// Adds a '$type' property to all classes that implement <see cref="IPolymorphicallySerialisable"/>.
  778. /// </summary>
  779. public class PolymorphicConverter : JsonConverter<object>
  780. {
  781. public override bool CanConvert(Type typeToConvert)
  782. {
  783. return typeof(IPolymorphicallySerialisable).IsAssignableFrom(typeToConvert) && (typeToConvert.IsInterface || typeToConvert.IsAbstract);
  784. }
  785. public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  786. {
  787. var dictionary = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(ref reader, options);
  788. if (dictionary is null) return null;
  789. if(dictionary.TryGetValue("$type", out var typeName))
  790. {
  791. var type = Type.GetType(typeName.GetString() ?? "")!;
  792. dictionary.Remove("$type");
  793. var data = JsonSerializer.Serialize(dictionary, options);
  794. return JsonSerializer.Deserialize(data, type, options);
  795. }
  796. else
  797. {
  798. return null;
  799. }
  800. }
  801. public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
  802. {
  803. writer.WriteStartObject();
  804. writer.WriteString("$type", value.GetType().AssemblyQualifiedName);
  805. var internalSerialisation = JsonSerializer.Serialize(value, options)[1..^1];
  806. if (!internalSerialisation.IsNullOrWhiteSpace())
  807. {
  808. writer.WriteRawValue(internalSerialisation, true);
  809. }
  810. writer.WriteEndObject();
  811. }
  812. }
  813. public abstract class CustomJsonConverter<T> : JsonConverter<T>
  814. {
  815. protected object? ReadJson(ref Utf8JsonReader reader)
  816. {
  817. switch (reader.TokenType)
  818. {
  819. case JsonTokenType.String:
  820. return reader.GetString();
  821. case JsonTokenType.Number:
  822. if (reader.TryGetInt32(out int intValue))
  823. return intValue;
  824. if (reader.TryGetDouble(out double doubleValue))
  825. return doubleValue;
  826. return null;
  827. case JsonTokenType.True:
  828. return true;
  829. case JsonTokenType.False:
  830. return false;
  831. case JsonTokenType.Null:
  832. return null;
  833. case JsonTokenType.StartArray:
  834. var values = new List<object?>();
  835. reader.Read();
  836. while(reader.TokenType != JsonTokenType.EndArray)
  837. {
  838. values.Add(ReadJson(ref reader));
  839. reader.Read();
  840. }
  841. return values;
  842. default:
  843. return null;
  844. }
  845. }
  846. protected T ReadEnum<T>(ref Utf8JsonReader reader)
  847. where T : struct
  848. {
  849. if(reader.TokenType == JsonTokenType.Number)
  850. {
  851. return (T)Enum.ToObject(typeof(T), reader.GetInt32());
  852. }
  853. else
  854. {
  855. return Enum.Parse<T>(reader.GetString());
  856. }
  857. }
  858. /// <summary>
  859. /// Write a value as a JSON object; note that some data types, like
  860. /// <see cref="Guid"/> and <see cref="DateTime"/> will be encoded as
  861. /// strings, and therefore will be returned as strings when read by
  862. /// <see cref="ReadJson(Utf8JsonReader)"/>. However, all types that
  863. /// this can write should be able to be retrieved by calling <see
  864. /// cref="CoreUtils.ChangeType(object?, Type)"/> on the resultant
  865. /// value.
  866. /// </summary>
  867. protected void WriteJson(Utf8JsonWriter writer, object? value)
  868. {
  869. if (value == null)
  870. writer.WriteNullValue();
  871. else if (value is string sVal)
  872. writer.WriteStringValue(sVal);
  873. else if (value is bool bVal)
  874. writer.WriteBooleanValue(bVal);
  875. else if (value is byte b)
  876. writer.WriteNumberValue(b);
  877. else if (value is short i16)
  878. writer.WriteNumberValue(i16);
  879. else if (value is int i32)
  880. writer.WriteNumberValue(i32);
  881. else if (value is long i64)
  882. writer.WriteNumberValue(i64);
  883. else if (value is float f)
  884. writer.WriteNumberValue(f);
  885. else if (value is double dVal)
  886. writer.WriteNumberValue(dVal);
  887. else if (value is DateTime dtVal)
  888. writer.WriteStringValue(dtVal.ToString());
  889. else if (value is TimeSpan tsVal)
  890. writer.WriteStringValue(tsVal.ToString());
  891. else if (value is Guid guid)
  892. writer.WriteStringValue(guid.ToString());
  893. else if(value is byte[] arr)
  894. {
  895. writer.WriteBase64StringValue(arr);
  896. }
  897. else if(value is Array array)
  898. {
  899. writer.WriteStartArray();
  900. foreach(var val1 in array)
  901. {
  902. WriteJson(writer, val1);
  903. }
  904. writer.WriteEndArray();
  905. }
  906. else if(value is Enum e)
  907. {
  908. WriteJson(writer, Convert.ChangeType(e, e.GetType().GetEnumUnderlyingType()));
  909. }
  910. else
  911. {
  912. Logger.Send(LogType.Error, "", $"Could not write object of type {value.GetType()} as JSON");
  913. }
  914. }
  915. protected void WriteJson(Utf8JsonWriter writer, string name, object? value)
  916. {
  917. writer.WritePropertyName(name);
  918. WriteJson(writer, value);
  919. }
  920. }
  921. public class TypeJsonConverter : CustomJsonConverter<Type>
  922. {
  923. public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  924. {
  925. if(reader.TokenType == JsonTokenType.String)
  926. {
  927. return Type.GetType(reader.GetString());
  928. }
  929. else
  930. {
  931. return null;
  932. }
  933. }
  934. public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
  935. {
  936. writer.WriteStringValue(value.AssemblyQualifiedName);
  937. }
  938. }
  939. }