Serialization.cs 39 KB

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