|
@@ -16,9 +16,9 @@ namespace InABox.Core
|
|
|
{
|
|
|
public interface ISerializeBinary
|
|
|
{
|
|
|
- public void SerializeBinary(BinaryWriter writer);
|
|
|
+ public void SerializeBinary(CoreBinaryWriter writer);
|
|
|
|
|
|
- public void DeserializeBinary(BinaryReader reader);
|
|
|
+ public void DeserializeBinary(CoreBinaryReader reader);
|
|
|
}
|
|
|
|
|
|
public static class Serialization
|
|
@@ -193,33 +193,101 @@ namespace InABox.Core
|
|
|
|
|
|
#region Binary Serialization
|
|
|
|
|
|
- public static byte[] WriteBinary(this ISerializeBinary obj)
|
|
|
+ public static byte[] WriteBinary(this ISerializeBinary obj, BinarySerializationSettings settings)
|
|
|
{
|
|
|
using var stream = new MemoryStream();
|
|
|
- obj.SerializeBinary(new BinaryWriter(stream));
|
|
|
+ obj.SerializeBinary(new CoreBinaryWriter(stream, settings));
|
|
|
return stream.ToArray();
|
|
|
}
|
|
|
|
|
|
- public static T ReadBinary<T>(byte[] data)
|
|
|
- where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data);
|
|
|
- public static T ReadBinary<T>(Stream stream)
|
|
|
- where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream);
|
|
|
+ public static T ReadBinary<T>(byte[] data, BinarySerializationSettings settings)
|
|
|
+ where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data, settings);
|
|
|
+ public static T ReadBinary<T>(Stream stream, BinarySerializationSettings settings)
|
|
|
+ where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream, settings);
|
|
|
|
|
|
- public static object ReadBinary(Type T, byte[] data)
|
|
|
+ public static object ReadBinary(Type T, byte[] data, BinarySerializationSettings settings)
|
|
|
{
|
|
|
using var stream = new MemoryStream(data);
|
|
|
- return ReadBinary(T, stream);
|
|
|
+ return ReadBinary(T, stream, settings);
|
|
|
}
|
|
|
- public static object ReadBinary(Type T, Stream stream)
|
|
|
+ public static object ReadBinary(Type T, Stream stream, BinarySerializationSettings settings)
|
|
|
{
|
|
|
var obj = (Activator.CreateInstance(T) as ISerializeBinary)!;
|
|
|
- obj.DeserializeBinary(new BinaryReader(stream));
|
|
|
+ obj.DeserializeBinary(new CoreBinaryReader(stream, settings));
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
|
+ public class CoreBinaryReader : BinaryReader
|
|
|
+ {
|
|
|
+ public BinarySerializationSettings Settings { get; set; }
|
|
|
+
|
|
|
+ public CoreBinaryReader(Stream stream, BinarySerializationSettings settings): base(stream)
|
|
|
+ {
|
|
|
+ Settings = settings;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ public class CoreBinaryWriter : BinaryWriter
|
|
|
+ {
|
|
|
+ public BinarySerializationSettings Settings { get; set; }
|
|
|
+
|
|
|
+ public CoreBinaryWriter(Stream stream, BinarySerializationSettings settings): base(stream)
|
|
|
+ {
|
|
|
+ Settings = settings;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A class to maintain the consistency of serialisation formats across versions.
|
|
|
+ /// The design of this is such that specific versions of serialisation have different parameters set,
|
|
|
+ /// and the versions are maintained as static properties. Please keep the constructor private.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Note that <see cref="Latest"/> should always be updated to point to the latest version.
|
|
|
+ /// <br/>
|
|
|
+ /// Note also that all versions should have an entry in the <see cref="ConvertVersionString(string)"/> function.
|
|
|
+ /// <br/>
|
|
|
+ /// Also, if you create a new format, it would probably be a good idea to add a database update script to get all
|
|
|
+ /// <see cref="IPackable"/> and <see cref="ISerializeBinary"/> properties and update the version of the format.
|
|
|
+ /// (Otherwise, we'd basically be nullifying all data that is currently binary serialised.)
|
|
|
+ /// </remarks>
|
|
|
+ public class BinarySerializationSettings
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Should reference types include a flag for nullability? (Adds an extra boolean field for whether the value is null or not).
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// True in all serialisation versions >= 1.1.
|
|
|
+ /// </remarks>
|
|
|
+ public bool IncludeNullables { get; set; }
|
|
|
+ public string Version { get; set; }
|
|
|
+
|
|
|
+ public static BinarySerializationSettings Latest => V1_1;
|
|
|
+
|
|
|
+ public static BinarySerializationSettings V1_0 = new BinarySerializationSettings("1.0")
|
|
|
+ {
|
|
|
+ IncludeNullables = false
|
|
|
+ };
|
|
|
+ public static BinarySerializationSettings V1_1 = new BinarySerializationSettings("1.1")
|
|
|
+ {
|
|
|
+ IncludeNullables = true
|
|
|
+ };
|
|
|
+
|
|
|
+ public static BinarySerializationSettings ConvertVersionString(string version) => version switch
|
|
|
+ {
|
|
|
+ "1.0" => V1_0,
|
|
|
+ "1.1" => V1_1,
|
|
|
+ _ => V1_0
|
|
|
+ };
|
|
|
+
|
|
|
+ private BinarySerializationSettings(string version)
|
|
|
+ {
|
|
|
+ Version = version;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static class SerializationUtils
|
|
|
{
|
|
|
public static void Write(this BinaryWriter writer, Guid guid)
|
|
@@ -232,8 +300,8 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Binary serialize a bunch of different types of values. <see cref="WriteBinaryValue(BinaryWriter, Type, object?)"/> and
|
|
|
- /// <see cref="ReadBinaryValue(BinaryReader, Type)"/> are inverses of each other.
|
|
|
+ /// Binary serialize a bunch of different types of values. <see cref="WriteBinaryValue(CoreBinaryWriter, Type, object?)"/> and
|
|
|
+ /// <see cref="ReadBinaryValue(CoreBinaryReader, Type)"/> are inverses of each other.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
|
|
@@ -245,7 +313,7 @@ namespace InABox.Core
|
|
|
/// <param name="type"></param>
|
|
|
/// <param name="value"></param>
|
|
|
/// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be serialized.</exception>
|
|
|
- public static void WriteBinaryValue(this BinaryWriter writer, Type type, object? value)
|
|
|
+ public static void WriteBinaryValue(this CoreBinaryWriter writer, Type type, object? value)
|
|
|
{
|
|
|
value ??= CoreUtils.GetDefault(type);
|
|
|
if (type == typeof(byte[]) && value is byte[] bArray)
|
|
@@ -329,19 +397,25 @@ namespace InABox.Core
|
|
|
}
|
|
|
else if (typeof(IPackable).IsAssignableFrom(type) && value is IPackable pack)
|
|
|
{
|
|
|
- writer.Write(true);
|
|
|
+ if (writer.Settings.IncludeNullables)
|
|
|
+ {
|
|
|
+ writer.Write(true);
|
|
|
+ }
|
|
|
pack.Pack(writer);
|
|
|
}
|
|
|
- else if (typeof(IPackable).IsAssignableFrom(type) && value is null)
|
|
|
+ else if (writer.Settings.IncludeNullables && typeof(IPackable).IsAssignableFrom(type) && value is null)
|
|
|
{
|
|
|
writer.Write(false);
|
|
|
}
|
|
|
else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is ISerializeBinary binary)
|
|
|
{
|
|
|
- writer.Write(true);
|
|
|
+ if (writer.Settings.IncludeNullables)
|
|
|
+ {
|
|
|
+ writer.Write(true);
|
|
|
+ }
|
|
|
binary.SerializeBinary(writer);
|
|
|
}
|
|
|
- else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is null)
|
|
|
+ else if (writer.Settings.IncludeNullables && typeof(ISerializeBinary).IsAssignableFrom(type) && value is null)
|
|
|
{
|
|
|
writer.Write(false);
|
|
|
}
|
|
@@ -364,8 +438,8 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Binary deserialize a bunch of different types of values. <see cref="WriteBinaryValue(BinaryWriter, Type, object?)"/> and
|
|
|
- /// <see cref="ReadBinaryValue(BinaryReader, Type)"/> are inverses of each other.
|
|
|
+ /// Binary deserialize a bunch of different types of values. <see cref="WriteBinaryValue(CoreBinaryWriter, Type, object?)"/> and
|
|
|
+ /// <see cref="ReadBinaryValue(CoreBinaryReader, Type)"/> are inverses of each other.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
|
|
@@ -373,11 +447,10 @@ namespace InABox.Core
|
|
|
/// <see cref="DateTime"/>, <see cref="TimeSpan"/>, <see cref="LoggablePropertyAttribute"/>, <see cref="IPackable"/>, <see cref="Nullable{T}"/>
|
|
|
/// and <see cref="ISerializeBinary"/>.
|
|
|
/// </remarks>
|
|
|
- /// <param name="writer"></param>
|
|
|
+ /// <param name="reader"></param>
|
|
|
/// <param name="type"></param>
|
|
|
- /// <param name="value"></param>
|
|
|
/// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be deserialized.</exception>
|
|
|
- public static object? ReadBinaryValue(this BinaryReader reader, Type type)
|
|
|
+ public static object? ReadBinaryValue(this CoreBinaryReader reader, Type type)
|
|
|
{
|
|
|
if (type == typeof(byte[]))
|
|
|
{
|
|
@@ -454,7 +527,7 @@ namespace InABox.Core
|
|
|
}
|
|
|
else if (typeof(IPackable).IsAssignableFrom(type))
|
|
|
{
|
|
|
- if (reader.ReadBoolean())
|
|
|
+ if (!reader.Settings.IncludeNullables || reader.ReadBoolean()) // Note the short-circuit operator preventing reading a boolean.
|
|
|
{
|
|
|
var packable = (Activator.CreateInstance(type) as IPackable)!;
|
|
|
packable.Unpack(reader);
|
|
@@ -467,7 +540,7 @@ namespace InABox.Core
|
|
|
}
|
|
|
else if (typeof(ISerializeBinary).IsAssignableFrom(type))
|
|
|
{
|
|
|
- if (reader.ReadBoolean())
|
|
|
+ if (!reader.Settings.IncludeNullables || reader.ReadBoolean()) // Note the short-circuit operator preventing reading a boolean.
|
|
|
{
|
|
|
var obj = (Activator.CreateInstance(type) as ISerializeBinary)!;
|
|
|
obj.DeserializeBinary(reader);
|
|
@@ -500,7 +573,7 @@ namespace InABox.Core
|
|
|
DatabaseSchema.Properties(type)
|
|
|
.Where(x => !(x is StandardProperty st) || st.Property.GetCustomAttribute<DoNotSerialize>() == null);
|
|
|
|
|
|
- private static void WriteOriginalValues<TObject>(this BinaryWriter writer, TObject obj)
|
|
|
+ private static void WriteOriginalValues<TObject>(this CoreBinaryWriter writer, TObject obj)
|
|
|
where TObject : BaseObject
|
|
|
{
|
|
|
var originalValues = new List<Tuple<Type, string, object?>>();
|
|
@@ -519,7 +592,7 @@ namespace InABox.Core
|
|
|
writer.WriteBinaryValue(type, value);
|
|
|
}
|
|
|
}
|
|
|
- private static void ReadOriginalValues<TObject>(this BinaryReader reader, TObject obj)
|
|
|
+ private static void ReadOriginalValues<TObject>(this CoreBinaryReader reader, TObject obj)
|
|
|
where TObject : BaseObject
|
|
|
{
|
|
|
var nOriginalValues = reader.ReadInt32();
|
|
@@ -534,7 +607,7 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// An implementation of binary serialising a <typeparamref name="TObject"/>; this is the inverse of <see cref="ReadObject{TObject}(BinaryReader)"/>.
|
|
|
+ /// An implementation of binary serialising a <typeparamref name="TObject"/>; this is the inverse of <see cref="ReadObject{TObject}(CoreBinaryReader)"/>.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// Also serialises the names of properties along with the values.
|
|
@@ -542,7 +615,7 @@ namespace InABox.Core
|
|
|
/// <typeparam name="TObject"></typeparam>
|
|
|
/// <param name="writer"></param>
|
|
|
/// <param name="entity"></param>
|
|
|
- public static void WriteObject<TObject>(this BinaryWriter writer, TObject entity)
|
|
|
+ public static void WriteObject<TObject>(this CoreBinaryWriter writer, TObject entity)
|
|
|
where TObject : BaseObject, new()
|
|
|
{
|
|
|
var properties = SerializableProperties(typeof(TObject)).ToList();
|
|
@@ -557,12 +630,12 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// The inverse of <see cref="WriteObject{TObject}(BinaryWriter, TObject)"/>.
|
|
|
+ /// The inverse of <see cref="WriteObject{TObject}(CoreBinaryWriter, TObject)"/>.
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TObject"></typeparam>
|
|
|
/// <param name="reader"></param>
|
|
|
/// <returns></returns>
|
|
|
- public static TObject ReadObject<TObject>(this BinaryReader reader)
|
|
|
+ public static TObject ReadObject<TObject>(this CoreBinaryReader reader)
|
|
|
where TObject : BaseObject, new()
|
|
|
{
|
|
|
var obj = new TObject();
|
|
@@ -581,7 +654,7 @@ namespace InABox.Core
|
|
|
|
|
|
/// <summary>
|
|
|
/// An implementation of binary serialising multiple <typeparamref name="TObject"/>s;
|
|
|
- /// this is the inverse of <see cref="ReadObjects{TObject}(BinaryReader)"/>.
|
|
|
+ /// this is the inverse of <see cref="ReadObjects{TObject}(CoreBinaryReader)"/>.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// Also serialises the names of properties along with the values.
|
|
@@ -589,7 +662,7 @@ namespace InABox.Core
|
|
|
/// <typeparam name="TObject"></typeparam>
|
|
|
/// <param name="writer"></param>
|
|
|
/// <param name="entity"></param>
|
|
|
- public static void WriteObjects<TObject>(this BinaryWriter writer, ICollection<TObject> objects)
|
|
|
+ public static void WriteObjects<TObject>(this CoreBinaryWriter writer, ICollection<TObject> objects)
|
|
|
where TObject : BaseObject, new()
|
|
|
{
|
|
|
var properties = SerializableProperties(typeof(TObject)).ToList();
|
|
@@ -612,12 +685,12 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// The inverse of <see cref="WriteObjects{TObject}(BinaryWriter, IList{TObject})"/>.
|
|
|
+ /// The inverse of <see cref="WriteObjects{TObject}(CoreBinaryWriter, IList{TObject})"/>.
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TObject"></typeparam>
|
|
|
/// <param name="reader"></param>
|
|
|
/// <returns></returns>
|
|
|
- public static List<TObject> ReadObjects<TObject>(this BinaryReader reader)
|
|
|
+ public static List<TObject> ReadObjects<TObject>(this CoreBinaryReader reader)
|
|
|
where TObject : BaseObject, new()
|
|
|
{
|
|
|
var objs = new List<TObject>();
|