0

I want to serialize my model objects (from WPF MVVM) which contains pure data. This sounds easy but I don't want to use the Serialization attributes and stuff provided in .NET framework. I just want to serialize it using my own way.
So here's a simplified version of one of my classes.

public class EntryKeyValuePair 
{
    public EntryKeyValuePair(string key, string value, bool isMultiline = false, bool isMandatory = true, bool isProtected = false)
    {
        Key = key;
        Value = value;
        IsMultiline = isMultiline;
        IsMandatory = isMandatory;
        IsProtected = isProtected;
    }

    public string Key { get; set; }
    public string Value { get; set; }
    public bool IsMultiline { get; set; }
    public bool IsMandatory { get; set; }
    public bool IsProtected { get; set; }

    public static EntryKeyValuePair FromXML(XElement element, ICipher cipher)
    {
        string key = cipher.Decrypt(element.Element(nameof(Key)).Value);
        string value = cipher.Decrypt(element.Element(nameof(Value)).Value);
        bool isMultiline = bool.Parse(element.Element(nameof(IsMultiline)).Value);
        bool isMandatory = bool.Parse(element.Element(nameof(IsMandatory)).Value);
        bool isProtected = bool.Parse(element.Element(nameof(IsProtected)).Value);
        return new EntryKeyValuePair(key, value, isMultiline, isMandatory, isProtected);
    }

    public XElement ToXML(ICipher cipher)
    {
        return new XElement(nameof(EntryKeyValuePair),
                                new XElement(nameof(Key),cipher.Encrypt(Key)),
                                new XElement(nameof(Value), cipher.Encrypt(Value)),
                                new XElement(nameof(IsMultiline), IsMultiline), new XElement(nameof(IsMandatory), IsMandatory),
                                new XElement(nameof(IsProtected), IsProtected));
    }
}

This works quite well. But this violates single responsibility principle and maybe other principles as well. This is also difficult to maintain and extend.
So I wanted to find another way. And here it is:

First I defined an IStringFormatter interface which can format the data to any string data formats like XML and JSON. (Not sure tho)

interface IStringFormatter
{
    string Name { get; set; }
    Dictionary<string, string> FieldDictionary { get; }
    string Format();
}

Here's how the XMLStringFormatter looks like:

class XmlStringFormatter : IStringFormatter
{
    public XmlStringFormatter()
    {
        FieldDictionary = new Dictionary<string, string>();
    }

    public string Name { get; set; }
    public Dictionary<string, string> FieldDictionary { get; }

    public string Format()
    {
        var xElement = new XElement(Name, FieldDictionary.Keys.Select(key => new XElement(key, FieldDictionary[key])));
        return xElement.ToString();
    }
}

Then I defined an ISerializer to serialize (or rather save) my data objects to the IStringFormatter.

interface ISerializer<T>
{
    T DeSerialize(IStringFormatter stringFormatter);
    void Serialize(T obj, IStringFormatter stringFormatter);
}

And here is how I "Serialize" EntryKeyValurPair by implementing this:

internal class EntryKeyValurPairSerializer : ISerializer<EntryKeyValuePair>
{
    public EntryKeyValuePair DeSerialize(IStringFormatter stringFormatter)
    {
        Dictionary<string, string> fieldDictionary = stringFormatter.FieldDictionary;

        try {
            string key = fieldDictionary[nameof(EntryKeyValuePair.Key)];
            string value = fieldDictionary[nameof(EntryKeyValuePair.Value)];
            bool isMandatory = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsMandatory)]);
            bool isProtected = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsProtected)]);
            bool isMultiline = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsMultiline)]);
            return new EntryKeyValuePair(key, value, isMultiline, isMandatory, isProtected);
        }
        catch (KeyNotFoundException ex) {
            throw new SerializationException(ex);
        }
        catch (FormatException ex) {
            throw new SerializationException(ex);
        }
    }

    public void Serialize(EntryKeyValuePair obj, IStringFormatter stringFormatter)
    {
        stringFormatter.Name = nameof(EntryKeyValuePair);
        Dictionary<string, string> fieldDictionary = stringFormatter.FieldDictionary;

        fieldDictionary.Add(nameof(EntryKeyValuePair.Key), obj.Key);
        fieldDictionary.Add(nameof(EntryKeyValuePair.Value), obj.Value);
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsMandatory), obj.IsMandatory.ToString());
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsProtected), obj.IsProtected.ToString());
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsMultiline), obj.IsMultiline.ToString());
    }
}

Now this works fine. But the problem is when I have a complex type like List<Entry> (where Entry is another data class) in my data classes.
As the IStringFormatter contains a Dictionary<string, string>, I can't just convert a List<Entry> to a string because I don't know what kind of IStringFormatter it wants in the context of ISerializer. How can I fix this? I also want to know if my solution is adhering to SOLID principles. If you can suggest a better solution (NOT the typical .NET serialization), I would appreciate it.

wingerse
  • 3,670
  • 1
  • 29
  • 61

1 Answers1

3

Writing your own serializer might be an interesting task, but I doubt that this is a good idea.

As I understood you want to keep your models clean, without any serialization specific attributes. I guess by "typical .NET serialization" you mean methods included with .Net framework.

For simplicity we will use these simple classes as an example:

class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
    public List<Order> Orders { get; set; }     
}

class Order 
{
    public int Id { get; set; }
    public string Details { get; set; }
}

An easy option would be to use Json.NET:

var customer = new Customer 
{ 
    Name = "Darth Vader", 
    Age = 45,
    Orders = new List<Order> 
    {
        new Order { Id = 1, Details = "Order1" },
        new Order { Id = 2, Details = "Order2" }
    }
};
string json = JsonConvert.SerializeObject(customer);

So as you can see you don't need to add any custom attributes to Customer class. It will work until you want to serialize all public properties.

The resulting JSON will be:

{
  "Name": "Darth Vader",
  "Age": 45,
  "Orders": [
    {
      "Id": 1,
      "Details": "Order1"
    },
    {
      "Id": 2,
      "Details": "Order2"
    }
  ]
}

After that you can always deserialize it:

var customer = JsonConvert.DeserializeObject<Customer>(json);

Lets say that you don't want Age property to be included. In this case I would suggest to create a different class that will be used for serialization only:

class CostomerSerializationContract
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

This main advantage if this approach is that you have separate class for serialization, and you can add any custom attributes there, if you choose to use some other serializer, without violating SOLID principle. The main disadvantage is that you need to keep both objects in sync manually.

You can use AutoMapper to reduce manual work when creating serialization contract from source class.

Aleksandr Ivanov
  • 2,778
  • 5
  • 27
  • 35
  • Will this work with List as well? It also have public get, set properties – wingerse Jan 21 '16 at 08:21
  • Yes, it will. I added an example to my answer. – Aleksandr Ivanov Jan 21 '16 at 08:54
  • What about if I have a `BitmapSource` (Which can't be serialized) property? . I can convert it to base64, but how can I tell the serializer to do it? – wingerse Jan 21 '16 at 17:01
  • Nvm, read it in (this) [http://stackoverflow.com/questions/18841690/serialize-and-store-an-image-in-an-xml-file] answer – wingerse Jan 21 '16 at 17:19
  • Just one more question. How should I use the SerializationContract classes? Should I instantiate those classes from the original class during serialization and instantiate the original class using contract classes during deserialization? – wingerse Jan 21 '16 at 17:28
  • 1
    You can do it manually, or use AutoMapper for example. So to create serialziaztion contract from Customer class: `var contract = Mapper.Map(customer);` You can get more information in their [getting started guide](https://github.com/AutoMapper/AutoMapper/wiki/Getting-started). – Aleksandr Ivanov Jan 21 '16 at 21:05
  • Thank you. I got everything working now. I found using separate contract classes really useful :) – wingerse Jan 21 '16 at 22:45