mirror of
https://github.com/DeclanHoare/whoa.git
synced 2025-01-22 06:01:49 -05:00
you don't need OrderAttribute!
This commit is contained in:
parent
f2b90d32bb
commit
b77b3a1198
6 changed files with 407 additions and 204 deletions
70
README.md
70
README.md
|
@ -1,26 +1,72 @@
|
|||
# Whoa
|
||||
|
||||
## About
|
||||
Whoa is a serialisation library for C#. Its output is ultra small and
|
||||
Whoa is a serialisation library for .NET. Its output is ultra small and
|
||||
you can add members to your type and still be able to deserialise old
|
||||
versions. Since it stores the bare minimum of type data, it is a bit
|
||||
more finicky than other serialisation solutions like BinaryFormatter,
|
||||
but produces much smaller output.
|
||||
|
||||
The format may change in non-backwards-compatible ways sometimes as new
|
||||
features are added. To make sure your data can be deserialised, use the
|
||||
same version of Whoa for serialisation and deserialisation. That said,
|
||||
whenever feasible, backwards compatibility will be preserved, and
|
||||
eventually the format will be finalised.
|
||||
|
||||
## Usage
|
||||
|
||||
For Whoa to produce meaningful output, your type will need to have a
|
||||
public constructor with no arguments, and every field or property that
|
||||
you would like to save needs to be public (properties must have a public
|
||||
getter AND a public setter) and have a Whoa.OrderAttribute. Also, if you
|
||||
want to preserve backwards compatibility, don't rearrange or remove
|
||||
any of the members with OrderAttributes. You can, however, add new ones
|
||||
to the end of your class without issue.
|
||||
Whoa is static and stateless. To serialise an object:
|
||||
|
||||
To serialise an object:
|
||||
`Whoa.Whoa.SerialiseObject(outstream, obj);`
|
||||
`Whoa.Whoa.SerialiseObject(outstream, object[, options]);`
|
||||
|
||||
To deserialise an object:
|
||||
`Whoa.Whoa.DeserialiseObject<T>(instream);`
|
||||
... and to deserialise it:
|
||||
|
||||
`Whoa.Whoa.DeserialiseObject<type>(instream[, options]);`
|
||||
|
||||
There are also reflection-friendly interfaces to these functions:
|
||||
|
||||
`Whoa.Whoa.SerialiseObject(type, outstream, object[, options]);`
|
||||
|
||||
`Whoa.Whoa.DeserialiseObject(type, instream[, options]);`
|
||||
|
||||
If you'd like to take control of the serialisation of a particular
|
||||
class, you should define a derivative of ISpecialSerialiser, instantiate
|
||||
it, and pass it to Whoa with:
|
||||
|
||||
`Whoa.Whoa.RegisterSpecialSerialiser(instance);`
|
||||
|
||||
ISpecialSerialiser is a generic interface. Your derivative should fill
|
||||
in the type argument with the type you want to handle. Whoa contains
|
||||
inbuilt special serialisers for:
|
||||
|
||||
* System.Guid
|
||||
* System.BigInteger
|
||||
* System.String
|
||||
* System.DateTime
|
||||
* System.Drawing.Color
|
||||
* System.Drawing.Font
|
||||
* System.Drawing.Image
|
||||
* System.IO.Stream
|
||||
|
||||
It will also handle arrays, Lists, and Dictionaries of types it knows
|
||||
automatically, and numeric types and enums are passed to BinaryWriter.
|
||||
|
||||
By default, Whoa requires the use of an OrderAttribute on each member
|
||||
of your class that you would like serialised, and it will only touch
|
||||
public members. It is also perfectly happy to serialise classes that do
|
||||
not have a SerializableAttribute. These characteristics can be changed
|
||||
by passing flags from the SerialisationOptions enum.
|
||||
Of particular note is the NonSerialized mode. This will remove the
|
||||
requirement for Order attributes and instead (de)serialise the members
|
||||
in the order they are received from GetMembers(). The .NET documentation
|
||||
states that this order is arbitrary, but on both Microsoft's framework
|
||||
and Mono, it seems to always match declaration order, so it's not
|
||||
currently a practical issue. In this mode, you can exclude members from
|
||||
serialisation by giving them a NonSerializedAttribute.
|
||||
Also, remember that the options will need to be the same on
|
||||
serialisation and deserialisation.
|
||||
|
||||
## TODO
|
||||
|
||||
* Reference semantics
|
||||
* Remembering derived type information
|
||||
|
|
|
@ -19,6 +19,7 @@ using System.IO;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Drawing;
|
||||
using Whoa;
|
||||
|
||||
namespace Whoa.Tests
|
||||
|
@ -42,87 +43,95 @@ namespace Whoa.Tests
|
|||
|
||||
private class Record
|
||||
{
|
||||
[Order]
|
||||
|
||||
public string title;
|
||||
|
||||
[Order]
|
||||
|
||||
public string artist;
|
||||
|
||||
[Order]
|
||||
public int rpm;
|
||||
|
||||
[Order]
|
||||
public int rpm { get; set; }
|
||||
|
||||
|
||||
public double price;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool inLibrary;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool otherBool;
|
||||
|
||||
[Order]
|
||||
|
||||
public List<string> songs;
|
||||
|
||||
[Order]
|
||||
|
||||
public Guid guidForSomeReason;
|
||||
|
||||
[Order]
|
||||
|
||||
public List<bool> moreBools;
|
||||
|
||||
[Order]
|
||||
|
||||
public string awards;
|
||||
|
||||
[Order]
|
||||
|
||||
public int? profits;
|
||||
|
||||
[Order]
|
||||
|
||||
public int? losses;
|
||||
|
||||
[Order]
|
||||
|
||||
public List<int> somethingElse;
|
||||
|
||||
[Order]
|
||||
|
||||
public Dictionary<string, string> staff;
|
||||
|
||||
[Order]
|
||||
|
||||
public Dictionary<string, string> plausibleSampleData;
|
||||
|
||||
[Order]
|
||||
|
||||
public List<bool> notActuallyMoreBools;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolThree;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolFour;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolFive;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolSix;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolSeven;
|
||||
|
||||
[Order]
|
||||
|
||||
public bool boolEight;
|
||||
|
||||
[Order]
|
||||
|
||||
public BigInteger reallyReallyReallyReallyReallyReallyBigNumber;
|
||||
|
||||
[Order]
|
||||
|
||||
public DateTime releaseDate;
|
||||
|
||||
[Order]
|
||||
|
||||
public RecordType kind;
|
||||
|
||||
[Order]
|
||||
|
||||
public Details details;
|
||||
|
||||
[Order]
|
||||
|
||||
public string[] thisIsAnArrayNotAList;
|
||||
|
||||
public Image img;
|
||||
|
||||
public Color colour;
|
||||
|
||||
public Font font;
|
||||
|
||||
public Stream data;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string ret = $@"{title}
|
||||
|
@ -150,6 +159,8 @@ Released: {releaseDate}
|
|||
ret += $"{pair.Key} - {pair.Value}" + Environment.NewLine;
|
||||
ret += "Null dictionaries work: " + (plausibleSampleData == null ? "Yes" : "No") + Environment.NewLine;
|
||||
ret += "Null bool lists work: " + (notActuallyMoreBools == null ? "Yes" : "No") + Environment.NewLine;
|
||||
byte[] buf = new byte[data.Length];
|
||||
data.Read(buf, 0, buf.Length);
|
||||
ret += $@"boolThree = {boolThree}
|
||||
boolFour = {boolFour}
|
||||
boolFive = {boolFive}
|
||||
|
@ -162,13 +173,17 @@ Is catchy: {details.HasFlag(Details.Catchy)}.
|
|||
Is popular: {details.HasFlag(Details.Popular)}.
|
||||
Is terrible: {details.HasFlag(Details.Terrible)}.
|
||||
The second entry of an array that isn't a list is: {thisIsAnArrayNotAList[1]}
|
||||
{img.Width}
|
||||
{colour.R} {colour.G} {colour.B} {colour.A}
|
||||
{font}
|
||||
{System.Text.Encoding.UTF8.GetString(buf)}
|
||||
";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
using (var str = new MemoryStream())
|
||||
using (var str = args.Length > 0 ? (File.Open(args[0], FileMode.Create) as Stream) : (new MemoryStream() as Stream))
|
||||
{
|
||||
var rec = new Record()
|
||||
{
|
||||
|
@ -216,18 +231,16 @@ The second entry of an array that isn't a list is: {thisIsAnArrayNotAList[1]}
|
|||
"The freedom to study how the program works, and change it so it does your computing as you wish (freedom 1). Access to the source code is a precondition for this.",
|
||||
"The freedom to redistribute copies so you can help your neighbor (freedom 2).",
|
||||
"The freedom to distribute copies of your modified versions to others (freedom 3). By doing this you can give the whole community a chance to benefit from your changes. Access to the source code is a precondition for this."
|
||||
}
|
||||
},
|
||||
img = new Bitmap(800, 600),
|
||||
colour = Color.FromArgb(255, 127, 0),
|
||||
font = new Font("Times New Roman", 12),
|
||||
data = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("what's up, gamers?"))
|
||||
};
|
||||
string expected = rec.ToString();
|
||||
Whoa.SerialiseObject(str, rec);
|
||||
if (args.Length > 0) // Save output for debugging.
|
||||
{
|
||||
str.Position = 0;
|
||||
using (var fobj = File.OpenWrite(args[0]))
|
||||
str.CopyTo(fobj);
|
||||
}
|
||||
Whoa.SerialiseObject(str, rec, SerialisationOptions.NonSerialized);
|
||||
str.Position = 0;
|
||||
var res = Whoa.DeserialiseObject<Record>(str);
|
||||
var res = Whoa.DeserialiseObject<Record>(str, SerialisationOptions.NonSerialized);
|
||||
string actual = res.ToString();
|
||||
Console.Write("Test ");
|
||||
if (expected == actual)
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.4.0.0")]
|
||||
[assembly: AssemblyVersion("1.5.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.5.0.0")]
|
||||
|
|
444
Whoa/Whoa.cs
444
Whoa/Whoa.cs
|
@ -24,114 +24,243 @@ using System.Diagnostics;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Reflection;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace Whoa
|
||||
{
|
||||
public static class Whoa
|
||||
{
|
||||
private class SpecialSerialiserAttribute: Attribute
|
||||
{
|
||||
private enum SpecialSizes
|
||||
{
|
||||
public Type t { get; private set; }
|
||||
public SpecialSerialiserAttribute(Type in_t)
|
||||
Null = -1,
|
||||
ReferenceEqual = -2 // not used yet...
|
||||
}
|
||||
|
||||
private static Dictionary<Type, dynamic> SpecialSerialisers = new Dictionary<Type, dynamic>();
|
||||
|
||||
static Whoa()
|
||||
{
|
||||
foreach (Type t in typeof(Whoa).GetNestedTypes(BindingFlags.NonPublic))
|
||||
{
|
||||
t = in_t;
|
||||
}
|
||||
}
|
||||
|
||||
private class SpecialDeserialiserAttribute: Attribute
|
||||
{
|
||||
public Type t { get; private set; }
|
||||
public SpecialDeserialiserAttribute(Type in_t)
|
||||
{
|
||||
t = in_t;
|
||||
}
|
||||
}
|
||||
|
||||
[SpecialSerialiser(typeof(Guid))]
|
||||
private static void SerialiseGuid(Stream fobj, dynamic obj)
|
||||
{
|
||||
fobj.Write(obj.ToByteArray(), 0, 16);
|
||||
}
|
||||
|
||||
[SpecialDeserialiser(typeof(Guid))]
|
||||
private static object DeserialiseGuid(Stream fobj)
|
||||
{
|
||||
var guid = new byte[16];
|
||||
fobj.Read(guid, 0, 16);
|
||||
return new Guid(guid);
|
||||
}
|
||||
|
||||
[SpecialSerialiser(typeof(BigInteger))]
|
||||
private static void SerialiseBigInt(Stream fobj, dynamic obj)
|
||||
{
|
||||
SerialiseObject(fobj, new List<byte>(obj.ToByteArray()));
|
||||
}
|
||||
|
||||
[SpecialDeserialiser(typeof(BigInteger))]
|
||||
private static object DeserialiseBigInt(Stream fobj)
|
||||
{
|
||||
return new BigInteger((DeserialiseObject(typeof(List<byte>), fobj) as List<byte>).ToArray());
|
||||
}
|
||||
|
||||
[SpecialSerialiser(typeof(string))]
|
||||
private static void SerialiseString(Stream fobj, dynamic obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
byte[] bytes = null;
|
||||
int len;
|
||||
if (obj == null)
|
||||
len = -1;
|
||||
else
|
||||
if (t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISpecialSerialiser<>)))
|
||||
{
|
||||
bytes = Encoding.UTF8.GetBytes(obj);
|
||||
len = bytes.Length;
|
||||
}
|
||||
typeof(BinaryWriter).GetMethod("Write7BitEncodedInt", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(write, new object[] { len });
|
||||
if (bytes != null)
|
||||
fobj.Write(bytes, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
[SpecialDeserialiser(typeof(string))]
|
||||
private static object DeserialiseString(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
int len = (int)typeof(BinaryReader).GetMethod("Read7BitEncodedInt", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(read, new object[] { });
|
||||
if (len < 0)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
var bytes = new byte[len];
|
||||
fobj.Read(bytes, 0, len);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
RegisterSpecialSerialiser((dynamic)Activator.CreateInstance(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SpecialSerialiser(typeof(DateTime))]
|
||||
private static void SerialiseDateTime(Stream fobj, dynamic obj)
|
||||
public static void RegisterSpecialSerialiser<T>(ISpecialSerialiser<T> serialiser)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
write.Write(obj.ToBinary());
|
||||
SpecialSerialisers.Add(typeof(T), serialiser);
|
||||
}
|
||||
|
||||
[SpecialDeserialiser(typeof(DateTime))]
|
||||
private static object DeserialiseDateTime(Stream fobj)
|
||||
private class GuidSerialiser: ISpecialSerialiser<Guid>
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return DateTime.FromBinary(read.ReadInt64());
|
||||
public void SerialiseObject(Stream fobj, Guid obj)
|
||||
{
|
||||
fobj.Write(obj.ToByteArray(), 0, 16);
|
||||
}
|
||||
|
||||
public Guid DeserialiseObject(Stream fobj)
|
||||
{
|
||||
var guid = new byte[16];
|
||||
fobj.Read(guid, 0, 16);
|
||||
return new Guid(guid);
|
||||
}
|
||||
}
|
||||
|
||||
private static IOrderedEnumerable<MemberInfo> Members(Type t)
|
||||
private class BigIntegerSerialiser: ISpecialSerialiser<BigInteger>
|
||||
{
|
||||
return t.GetProperties().Select(m => m as MemberInfo).Concat(t.GetFields().Select(m => m as MemberInfo)).Where(m => (m.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() as OrderAttribute) != null).OrderBy(m => (m.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() as OrderAttribute).Order);
|
||||
public void SerialiseObject(Stream fobj, BigInteger obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
byte[] bytes = obj.ToByteArray();
|
||||
write.Write(bytes.Length);
|
||||
write.Write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public BigInteger DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return new BigInteger(read.ReadBytes(read.ReadInt32()));
|
||||
}
|
||||
}
|
||||
|
||||
private class StringSerialiser: ISpecialSerialiser<string>
|
||||
{
|
||||
private class FriendlyBinaryWriter: BinaryWriter
|
||||
{
|
||||
public FriendlyBinaryWriter(Stream fobj) : base(fobj, Encoding.UTF8, true)
|
||||
{
|
||||
}
|
||||
public void Write7BitEncodedIntPublic(int value)
|
||||
{
|
||||
Write7BitEncodedInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
private class FriendlyBinaryReader: BinaryReader
|
||||
{
|
||||
public FriendlyBinaryReader(Stream fobj) : base(fobj, Encoding.UTF8, true)
|
||||
{
|
||||
}
|
||||
public int Read7BitEncodedIntPublic()
|
||||
{
|
||||
return Read7BitEncodedInt();
|
||||
}
|
||||
}
|
||||
public void SerialiseObject(Stream fobj, string obj)
|
||||
{
|
||||
using (var write = new FriendlyBinaryWriter(fobj))
|
||||
{
|
||||
byte[] bytes = null;
|
||||
int len;
|
||||
if (obj == null)
|
||||
len = (int)SpecialSizes.Null;
|
||||
else
|
||||
{
|
||||
bytes = Encoding.UTF8.GetBytes(obj);
|
||||
len = bytes.Length;
|
||||
}
|
||||
write.Write7BitEncodedIntPublic(len);
|
||||
if (bytes != null)
|
||||
write.Write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public string DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new FriendlyBinaryReader(fobj))
|
||||
{
|
||||
int len = read.Read7BitEncodedIntPublic();
|
||||
if (len == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
return Encoding.UTF8.GetString(read.ReadBytes(len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DateTimeSerialiser: ISpecialSerialiser<DateTime>
|
||||
{
|
||||
public void SerialiseObject(Stream fobj, DateTime obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
write.Write(obj.ToBinary());
|
||||
}
|
||||
|
||||
public DateTime DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return DateTime.FromBinary(read.ReadInt64());
|
||||
}
|
||||
}
|
||||
|
||||
private class ColorSerialiser: ISpecialSerialiser<Color>
|
||||
{
|
||||
public void SerialiseObject(Stream fobj, Color obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
write.Write(obj.ToArgb());
|
||||
}
|
||||
|
||||
public Color DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return Color.FromArgb(read.ReadInt32());
|
||||
}
|
||||
}
|
||||
|
||||
private class FontSerialiser: ISpecialSerialiser<Font>
|
||||
{
|
||||
public void SerialiseObject(Stream fobj, Font obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
write.Write(obj.FontFamily.Name);
|
||||
write.Write(obj.Size);
|
||||
write.Write((int)obj.Style);
|
||||
write.Write((int)obj.Unit);
|
||||
write.Write(obj.GdiCharSet);
|
||||
write.Write(obj.GdiVerticalFont);
|
||||
}
|
||||
}
|
||||
|
||||
public Font DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return new Font(read.ReadString(), read.ReadSingle(), (FontStyle)read.ReadInt32(), (GraphicsUnit)read.ReadInt32(), read.ReadByte(), read.ReadBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
private class ImageSerialiser: ISpecialSerialiser<Image>
|
||||
{
|
||||
public void SerialiseObject(Stream fobj, Image obj)
|
||||
{
|
||||
// Images can be saved to Streams but the image data will
|
||||
// be "invalid" if the Stream it is written to starts at a
|
||||
// non-zero position, so we need to buffer.
|
||||
using (var mstr = new MemoryStream())
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
obj.Save(mstr, ImageFormat.Png);
|
||||
mstr.Position = 0;
|
||||
write.Write((int)mstr.Length);
|
||||
mstr.CopyTo(fobj);
|
||||
}
|
||||
}
|
||||
|
||||
public Image DeserialiseObject(Stream fobj)
|
||||
{
|
||||
// Images can be loaded from Streams but they must remain
|
||||
// open for the lifetime of the Image, so, to avoid needing
|
||||
// a handle on the file being loaded from, we buffer on read
|
||||
// too.
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return Image.FromStream(new MemoryStream(read.ReadBytes(read.ReadInt32())));
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamSerialiser: ISpecialSerialiser<Stream>
|
||||
{
|
||||
public void SerialiseObject(Stream fobj, Stream obj)
|
||||
{
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
obj.Position = 0;
|
||||
write.Write((int)obj.Length);
|
||||
obj.CopyTo(fobj);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream DeserialiseObject(Stream fobj)
|
||||
{
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
return new MemoryStream(read.ReadBytes(read.ReadInt32()));
|
||||
}
|
||||
}
|
||||
|
||||
private static IOrderedEnumerable<MemberInfo> Members(Type t, SerialisationOptions options)
|
||||
{
|
||||
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
|
||||
if (options.HasFlag(SerialisationOptions.NonPublic))
|
||||
flags |= BindingFlags.NonPublic;
|
||||
if (options.HasFlag(SerialisationOptions.FlattenHierarchy))
|
||||
flags |= BindingFlags.FlattenHierarchy;
|
||||
var all = t.GetMembers(flags).Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property);
|
||||
int i = 0;
|
||||
if (options.HasFlag(SerialisationOptions.NonSerialized))
|
||||
return all.Where(m => (m.GetCustomAttributes(typeof(NonSerializedAttribute), false).SingleOrDefault() == null)).OrderBy(m => i++);
|
||||
else
|
||||
return all.Where(m => (m.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() != null)).OrderBy(m => (m.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() as OrderAttribute).Order);
|
||||
}
|
||||
|
||||
private static List<bool> ReadBitfield(Stream fobj, int count)
|
||||
{
|
||||
if (count < 0)
|
||||
if (count == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
sbyte bit = 7;
|
||||
int cur = 0;
|
||||
|
@ -173,32 +302,39 @@ namespace Whoa
|
|||
fobj.Write(bitfields, 0, bitfields.Length);
|
||||
}
|
||||
|
||||
public static T DeserialiseObject<T>(Stream fobj)
|
||||
public static T DeserialiseObject<T>(Stream fobj, SerialisationOptions options = SerialisationOptions.None)
|
||||
{
|
||||
return (T)DeserialiseObject(typeof(T), fobj);
|
||||
return (T)DeserialiseObject(typeof(T), fobj, options);
|
||||
}
|
||||
|
||||
private static object DeserialiseObjectWorker(Type t, Stream fobj)
|
||||
private static object DeserialiseObjectWorker(Type t, Stream fobj, SerialisationOptions options)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("Deserialising object of type: " + t.ToString());
|
||||
#endif
|
||||
using (var read = new BinaryReader(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
// Look for a special serialiser for this type.
|
||||
dynamic special = null;
|
||||
if (SpecialSerialisers.TryGetValue(t, out special))
|
||||
{
|
||||
return special.DeserialiseObject(fobj);
|
||||
}
|
||||
|
||||
if (t.IsEnum)
|
||||
{
|
||||
return Enum.ToObject(t, DeserialiseObjectWorker(Enum.GetUnderlyingType(t), fobj));
|
||||
return Enum.ToObject(t, DeserialiseObjectWorker(Enum.GetUnderlyingType(t), fobj, options));
|
||||
}
|
||||
|
||||
if (t.IsArray)
|
||||
{
|
||||
int numelems = read.ReadInt32();
|
||||
if (numelems < 0)
|
||||
if (numelems == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
dynamic reta = Activator.CreateInstance(t, new object[] { numelems });
|
||||
Type elemtype = t.GetElementType();
|
||||
for (int i = 0; i < numelems; i++)
|
||||
reta[i] = (dynamic)DeserialiseObjectWorker(elemtype, fobj);
|
||||
reta[i] = (dynamic)DeserialiseObjectWorker(elemtype, fobj, options);
|
||||
return reta;
|
||||
}
|
||||
|
||||
|
@ -209,61 +345,49 @@ namespace Whoa
|
|||
if (gent == typeof(Nullable<>))
|
||||
{
|
||||
bool extant = read.ReadBoolean();
|
||||
return extant ? DeserialiseObjectWorker(t.GetGenericArguments()[0], fobj) : null;
|
||||
return extant ? DeserialiseObjectWorker(t.GetGenericArguments()[0], fobj, options) : null;
|
||||
}
|
||||
|
||||
if (gent == typeof(List<>))
|
||||
{
|
||||
int numelems = read.ReadInt32();
|
||||
if (numelems < 0)
|
||||
if (numelems == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
dynamic retl = Activator.CreateInstance(t, new object[] { numelems });
|
||||
Type elemtype = t.GetGenericArguments()[0];
|
||||
for (int i = 0; i < numelems; i++)
|
||||
retl.Add((dynamic)DeserialiseObjectWorker(elemtype, fobj));
|
||||
retl.Add((dynamic)DeserialiseObjectWorker(elemtype, fobj, options));
|
||||
return retl;
|
||||
}
|
||||
|
||||
if (gent == typeof(Dictionary<,>))
|
||||
{
|
||||
int numpairs = read.ReadInt32();
|
||||
if (numpairs < 0)
|
||||
if (numpairs == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
dynamic retd = Activator.CreateInstance(t, new object[] { numpairs });
|
||||
Type[] arguments = t.GetGenericArguments();
|
||||
for (int i = 0; i < numpairs; i++)
|
||||
{
|
||||
dynamic key = DeserialiseObjectWorker(arguments[0], fobj);
|
||||
dynamic val = DeserialiseObjectWorker(arguments[1], fobj);
|
||||
dynamic key = DeserialiseObjectWorker(arguments[0], fobj, options);
|
||||
dynamic val = DeserialiseObjectWorker(arguments[1], fobj, options);
|
||||
retd.Add(key, val);
|
||||
}
|
||||
return retd;
|
||||
}
|
||||
}
|
||||
|
||||
// A little self reflection.
|
||||
var specialmethod = typeof(Whoa).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).FirstOrDefault(m =>
|
||||
{
|
||||
var attr = m.GetCustomAttributes(typeof(SpecialDeserialiserAttribute), false).SingleOrDefault() as SpecialDeserialiserAttribute;
|
||||
if (attr == null)
|
||||
return false;
|
||||
return attr.t == t;
|
||||
});
|
||||
|
||||
if (specialmethod != null)
|
||||
return specialmethod.Invoke(null, new object[] { fobj });
|
||||
|
||||
var readermethod = typeof(BinaryReader).GetMethods().FirstOrDefault(m => m.Name.Length > 4 && m.Name.StartsWith("Read") && m.ReturnType == t);
|
||||
if (readermethod != null)
|
||||
return readermethod.Invoke(read, new object[] { });
|
||||
|
||||
int nummembers = read.ReadInt32();
|
||||
if (nummembers < 0)
|
||||
if (nummembers == (int)SpecialSizes.Null)
|
||||
return null;
|
||||
object ret = t.GetConstructor(Type.EmptyTypes).Invoke(new object[] { });
|
||||
var bools = new List<dynamic>();
|
||||
|
||||
foreach (dynamic member in Members(t).Take(nummembers))
|
||||
foreach (dynamic member in Members(t, options).Take(nummembers))
|
||||
{
|
||||
Type memt;
|
||||
if (member.MemberType == MemberTypes.Field)
|
||||
|
@ -278,7 +402,7 @@ namespace Whoa
|
|||
else if (memt == typeof(bool[]))
|
||||
member.SetValue(ret, ReadBitfield(fobj, read.ReadInt32()).ToArray());
|
||||
else
|
||||
member.SetValue(ret, DeserialiseObjectWorker(memt, fobj));
|
||||
member.SetValue(ret, DeserialiseObjectWorker(memt, fobj, options));
|
||||
}
|
||||
|
||||
if (bools.Count > 0)
|
||||
|
@ -291,22 +415,32 @@ namespace Whoa
|
|||
}
|
||||
}
|
||||
|
||||
public static void SerialiseObject(Stream fobj, dynamic obj)
|
||||
public static void SerialiseObject<T>(Stream fobj, T obj, SerialisationOptions options = SerialisationOptions.None)
|
||||
{
|
||||
SerialiseObject(fobj, obj, obj.GetType());
|
||||
Type t = typeof(T);
|
||||
if ((t == typeof(object)) && obj != null)
|
||||
t = obj.GetType();
|
||||
SerialiseObject(t, fobj, obj, options);
|
||||
}
|
||||
|
||||
private static void SerialiseObjectWorker(Stream fobj, dynamic obj, Type t)
|
||||
private static void SerialiseObjectWorker(Stream fobj, dynamic obj, Type t, SerialisationOptions options)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("Serialising object of type: " + t.ToString());
|
||||
#endif
|
||||
using (var write = new BinaryWriter(fobj, Encoding.UTF8, true))
|
||||
{
|
||||
// Look for a special serialiser for this type.
|
||||
dynamic special = null;
|
||||
if (SpecialSerialisers.TryGetValue(t, out special))
|
||||
{
|
||||
special.SerialiseObject(fobj, obj);
|
||||
return;
|
||||
}
|
||||
if (t.IsEnum)
|
||||
{
|
||||
Type realt = Enum.GetUnderlyingType(t);
|
||||
SerialiseObjectWorker(fobj, Convert.ChangeType(obj, realt), realt);
|
||||
SerialiseObjectWorker(fobj, Convert.ChangeType(obj, realt), realt, options);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -314,12 +448,12 @@ namespace Whoa
|
|||
{
|
||||
if (obj == null)
|
||||
{
|
||||
write.Write(-1);
|
||||
write.Write((int)SpecialSizes.Null);
|
||||
return;
|
||||
}
|
||||
write.Write(obj.Length);
|
||||
foreach (dynamic item in obj)
|
||||
SerialiseObjectWorker(fobj, item, item.GetType());
|
||||
SerialiseObjectWorker(fobj, item, item.GetType(), options);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -332,7 +466,7 @@ namespace Whoa
|
|||
bool extant = obj != null;
|
||||
write.Write(extant);
|
||||
if (extant)
|
||||
SerialiseObjectWorker(fobj, obj, t.GetGenericArguments()[0]);
|
||||
SerialiseObjectWorker(fobj, obj, t.GetGenericArguments()[0], options);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -340,12 +474,12 @@ namespace Whoa
|
|||
{
|
||||
if (obj == null)
|
||||
{
|
||||
write.Write(-1);
|
||||
write.Write((int)SpecialSizes.Null);
|
||||
return;
|
||||
}
|
||||
write.Write(obj.Count);
|
||||
foreach (dynamic item in obj)
|
||||
SerialiseObjectWorker(fobj, item, item.GetType());
|
||||
SerialiseObjectWorker(fobj, item, item.GetType(), options);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -353,36 +487,22 @@ namespace Whoa
|
|||
{
|
||||
if (obj == null)
|
||||
{
|
||||
write.Write(-1);
|
||||
write.Write((int)SpecialSizes.Null);
|
||||
return;
|
||||
}
|
||||
write.Write(obj.Count);
|
||||
foreach (dynamic pair in obj)
|
||||
{
|
||||
SerialiseObjectWorker(fobj, pair.Key, pair.Key.GetType());
|
||||
SerialiseObjectWorker(fobj, pair.Value, pair.Value.GetType());
|
||||
SerialiseObjectWorker(fobj, pair.Key, pair.Key.GetType(), options);
|
||||
SerialiseObjectWorker(fobj, pair.Value, pair.Value.GetType(), options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var specialmethod = typeof(Whoa).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).FirstOrDefault(m =>
|
||||
{
|
||||
var attr = m.GetCustomAttributes(typeof(SpecialSerialiserAttribute), false).SingleOrDefault() as SpecialSerialiserAttribute;
|
||||
if (attr == null)
|
||||
return false;
|
||||
return attr.t == t;
|
||||
});
|
||||
|
||||
if (specialmethod != null)
|
||||
{
|
||||
specialmethod.Invoke(null, new object[] { fobj, obj });
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
write.Write(obj); // Will fail if not an integral type
|
||||
write.Write(obj); // Will fail if not a primitive type
|
||||
return;
|
||||
}
|
||||
catch
|
||||
|
@ -391,12 +511,15 @@ namespace Whoa
|
|||
|
||||
if (obj == null)
|
||||
{
|
||||
write.Write(-1);
|
||||
write.Write((int)SpecialSizes.Null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.HasFlag(SerialisationOptions.RequireSerializable) && t.GetCustomAttributes(typeof(SerializableAttribute), false).SingleOrDefault() == null)
|
||||
throw new SerializationException($"{t} is not serialisable.");
|
||||
|
||||
var bools = new List<bool>();
|
||||
var members = Members(t);
|
||||
var members = Members(t, options);
|
||||
write.Write(members.Count());
|
||||
|
||||
foreach (dynamic member in members)
|
||||
|
@ -413,7 +536,7 @@ namespace Whoa
|
|||
{
|
||||
var val = member.GetValue(obj) as IEnumerable<bool>;
|
||||
if (val == null)
|
||||
write.Write(-1);
|
||||
write.Write((int)SpecialSizes.Null);
|
||||
else
|
||||
{
|
||||
write.Write(val.Count());
|
||||
|
@ -423,39 +546,56 @@ namespace Whoa
|
|||
else
|
||||
{
|
||||
dynamic val = member.GetValue(obj);
|
||||
SerialiseObjectWorker(fobj, val, memt);
|
||||
SerialiseObjectWorker(fobj, val, memt, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WriteBitfield(fobj, bools);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void SerialiseObject(Stream fobj, dynamic obj, Type t)
|
||||
public static void SerialiseObject(Type t, Stream fobj, dynamic obj, SerialisationOptions options = SerialisationOptions.None)
|
||||
{
|
||||
try
|
||||
{
|
||||
SerialiseObjectWorker(fobj, obj, t);
|
||||
SerialiseObjectWorker(fobj, obj, t, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Data.Add("Whoa: Type", t);
|
||||
ex.Data.Add("Whoa: Object", obj);
|
||||
try
|
||||
{
|
||||
ex.Data.Add("Whoa: Type", t);
|
||||
ex.Data.Add("Whoa: Object", obj);
|
||||
}
|
||||
catch // Stifle this exception and throw the important one
|
||||
{
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static object DeserialiseObject(Type t, Stream fobj)
|
||||
[Obsolete("This argument order is weird.")]
|
||||
public static void SerialiseObject(Stream fobj, dynamic obj, Type t)
|
||||
{
|
||||
SerialiseObject(t, fobj, obj);
|
||||
}
|
||||
|
||||
public static object DeserialiseObject(Type t, Stream fobj, SerialisationOptions options = SerialisationOptions.None)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DeserialiseObjectWorker(t, fobj);
|
||||
return DeserialiseObjectWorker(t, fobj, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Data.Add("Whoa: Type", t);
|
||||
try
|
||||
{
|
||||
ex.Data.Add("Whoa: Type", t);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,13 @@
|
|||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ISpecialSerialiser.cs" />
|
||||
<Compile Include="OrderAttribute.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SerialisationMode.cs" />
|
||||
<Compile Include="Whoa.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
Loading…
Reference in a new issue