Serialization.cs 44 KB

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