Introduction
Recently, I was tasked with having to serialize a large set of classes and detect changes to them at different points in time. This really involved two tasks: serialization and computing diffs on objects. Ordinarily, I would have jumped right into using ProtoBuf
and started adding contract attributes to all of my classes. This would take some time to do, and ultimately would be a pain because of the number of classes and properties involved I would need to change. It was also made difficult because ProtoBuf
doesn't like serializing interface types without defining all of the possible concrete classes that implement it. What I wanted was a way to just serialize the objects as-is, and not throw up if properties referenced interfaces or contained generic collections with interface references or circular references (Entity Framework data models for example).
Alternatives
I first tried using ProtoBuf
and attributing all of my classes with ProtoContracts
. This also required using ProtoInclude
on any interfaces to make ProtoBuf
aware of concrete classes that implement them. I also found myself having to exclude a lot of member fields that it didn't like (delegates, locks, etc.) and my code base was starting to get hairy.
After finally throwing my hands in the air with it, I thought I'd give Microsoft's BinaryFormatter
a try even though it generates much larger data and is not nearly as performant. At least here, I'd only have to add [Serializable]
attributes, right? Well, after going through several hundred classes and decorating with [NonSerializable]
on fields and [IgnoreDataMember]
on properties, it was starting to get hairy as well. I kind of had it working but it was painful and would blow up sometimes especially when I had to start modifying Entity Framework classes.
Using the Code
Getting started with AnySerializer
is really, really simple. Install the nuget package into your project using Package Manager Console: Install-Package AnySerializer
Serialize any object of your choosing like this:
using AnySerializer.Extensions;
var originalObject = new SomeComplexTypeWithDeepStructure();
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<SomeComplexTypeWithDeepStructure>();
And that is all that is needed to serialize your class!
Advanced Control
There may be cases where the plain usage doesn't work for your application. Such a use case would be interfaces with multiple concrete types. The serializer needs to be made aware which concrete type you would like to restore, but that doesn't mean you need to decorate it at compile time! Let's try another sample:
using AnySerializer.Extensions;
var originalObject = new VehicleContainer(new Car
{ Color = "Red", Make = "Dodge", Model = "Challenger", DoorCount = 4});
var bytes = originalObject.Serialize(SerializerOptions.EmbedTypes);
var restoredObject = bytes.Deserialize<VehicleContainer>(originalObject.GetType());
Assert.AreEqual(typeof(Car), restoredObject.Vehicle);
public class VehicleContainer
{
public IVehicle Vehicle { get; set; }
public VehicleContainer(IVehicle vehicle)
{
Vehicle = vehicle;
}
}
public interface IVehicle
{
string Color { get; }
string Make { get; }
string Model { get; }
}
public class Car : IVehicle
{
string Color { get; }
string Make { get; }
string Model { get; }
int DoorCount { get; }
}
public class Truck : IVehicle
{
string Color { get; }
string Make { get; }
string Model { get; }
string Is4x4 { get; }
}
See the difference? We need to pass SerializerOptions
to tell the Serializer to embed type information for any types that require it. This will include information only about types that need concrete type information in order to restore.
There are lots of other scenarios that can be at times challenging to serialize. Here are a few examples!
Serialize anonymous types:
using AnySerializer.Extensions;
var originalObject = new { Id = 1, Name = "John Doe" };
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize(originalObject.GetType());
Serialize any collection type (IEnumerable
, ICollection
, List
, Dictionary
, Hashtable
, etc.):
using AnySerializer.Extensions;
var colorsList = new List<string> { "Red", "Green", "Blue" };
var foodsDictionary = new Dictionary<string, string>
{ { "Tomato", "Red" }, { "Banana", "Yellow" } };
var colorsBytes = colorsList.Serialize();
var foodsDictionary = colorsList.Serialize();
var restoredColorsList = bytes.Deserialize<List<string>>();
var restoredFoodsDictionary = bytes.Deserialize<Dictionary<string,string>>();
Serialize tuples:
using AnySerializer.Extensions;
var originalObject = new Tuple<int, string>(1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<Tuple<int,string>>();
var originalObject = (1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<(int, string)>();
var originalObject = (id: 1, name: "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<(int, string)>();
Constructorless or constructors with parameters object:
using AnySerializer.Extensions;
var originalObject = ConstructorlessObject.Create();
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<ConstructorlessObject>();
public class ConstructorlessObject
{
private ConstructorlessObject() { }
public static ConstructorlessObject Create()
{
return new ConstructorlessObject();
}
}
var originalObject = new ConstructorWithParametersObject(1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<ConstructorlessObject>();
public class ConstructorWithParametersObject
{
public ConstructorlessObject(int id, string name) { }
}
Custom collections:
using AnySerializer.Extensions;
var originalObject = new CustomCollectionObject(1, 2);
originalObject.Add("test1", 1);
originalObject.Add("test2", 2);
originalObject.Add("test3", 3);
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<CustomCollectionObject>();
public class CustomCollectionObject : IEquatable<CustomCollectionObject>
{
private readonly Dictionary<string, int> _innerStorage = new Dictionary<string, int>();
private int _a;
private int _z;
public ICollection<string> Keys
{
get { return _innerStorage.Keys; }
}
public ICollection<int> Values
{
get { return _innerStorage.Values; }
}
public void Add(string key, int value)
{
if (!_innerStorage.ContainsKey(key))
{
_innerStorage.Add(key, value);
}
}
public int this[string key]
{
get
{
if (!_innerStorage.TryGetValue(key, out int item))
{
_innerStorage.Add(key, item);
}
return item;
}
set
{
_innerStorage[key] = value;
}
}
public CustomCollectionObject(int a, int z)
{
_a = a;
_z = z;
}
}
How Does It Stack Up Against Others?
Feature | AnySerializer | ProtoBuf | BinaryFormatter | ZeroFormatter |
Declarative-Free | X | | | |
Circular-References | X | X | | |
Interfaces | X | x | | |
Constructorless | X | x | | |
.Net Core | X | X | X | X |
.Net Framework | X | X | X | X |
Compression | X | | | |
Graph support | X | x | X | X |
Anonymous | X | | | |
Private | X | X | X | X |
Ignore Property | X | | | |
Ignore Field | X | | | |
Size | 59b | 32b | 414b | 414b |
Speed* Ser/Deser | 3 / 2 | 2 / 1 | 10 / 8 | 1 / 2 |
X - feature supported
x - indicates declarative support for feature
* - Speed references (Serialize / Deserialize) are ranked from 1 - 10, lower is better.
There are many scenarios that are difficult to do with other serializers that are made simple with AnySerializer
. Check out the descriptive Wiki for details on how to accomplish almost anything!
History
- 3rd December, 2018: Initial version