2

In my app I have a lot of XMLs to serialize from, so I have created quite a lot of models, to serialize to. I use System.Xml.Serialization, so it needs parameterless constructor and public access to properties to write deserialized data to.

Now, I want to implement Memento Pattern to provide Undo and Redo functionality, and in that case it would be good to be able to have immutable objects, so there would not be any bug related to that I changed some property instead of creating new instance.

The problem is I don't see any solution that would provide me both solutions at once - being able to deserialize objects and then provide immutability for Memento pattern (well, there is popsicle immutability but I am not sure about it). Creating separate objects (records or so) that my deserialized objects would be converted to after program is done with deserialization seems like solution, but it has a flaw o introducing a lot of new types that consume time and increase upkeep cost later.

Do you guys have other ideas or patterns that I could use?

Adam Kaczmarski
  • 338
  • 1
  • 12

1 Answers1

0

I was able to create a solution with the help of this article. The only caveat is it requires DataContractSerializer instead of System.Xml.Serialization. Here is a dotnetfiddle of my implementation.

public class DataContractXmlSerializer<T>
{
    private readonly DataContractSerializer _dataContractSerializer;

    public DataContractXmlSerializer()
    {
        _ = Attribute.GetCustomAttribute(typeof(T), typeof(DataContractAttribute))
        ?? throw new InvalidOperationException($"Type {typeof(T)} does not contain Attribute of type {typeof(DataContractAttribute)}.");

        _dataContractSerializer = new DataContractSerializer(typeof(T));
    }

    public T Deserialize(string xml)
    {
        var xmlData = Encoding.UTF8.GetBytes(xml);
        var xmlStream = new MemoryStream(xmlData);

        using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(xmlStream, Encoding.UTF8, new XmlDictionaryReaderQuotas(), null))
        {
            return (T)_dataContractSerializer.ReadObject(reader);
        }
    }

    public string Serialize(T obj)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            _dataContractSerializer.WriteObject(memoryStream, obj);
            return Encoding.UTF8.GetString(memoryStream.ToArray());
         }
    }
}

Update If you must use System.Xml.Serialization, I recommended using a layered approach to convert between your mutable and immutable models. Here is my dotnetfiddle of this approach.

public class ModelXmlSerializer : ImmutableXmlSerializer<ImmutableModel, Model>
{
    protected override ImmutableModel ConvertToImmutable(Model mutable)
    {
        return new ImmutableModel(mutable);
    }
    
    protected override Model ConvertToMutable(ImmutableModel immutable)
    {
        return new Model(immutable);
    }
}

public abstract class ImmutableXmlSerializer<TImmutable, TMutable>
{
    private readonly MyXmlSerializer<TMutable> _xmlSerializer;

    public ImmutableXmlSerializer()
    {
        _xmlSerializer = new MyXmlSerializer<TMutable>();
    }

    public TImmutable Deserialize(string xml)
    {
        return ConvertToImmutable(_xmlSerializer.Deserialize(xml));
    }

    public string Serialize(TImmutable obj)
    {
        return _xmlSerializer.Serialize(ConvertToMutable(obj));
    }
    
    protected abstract TImmutable ConvertToImmutable(TMutable mutable);
    protected abstract TMutable ConvertToMutable(TImmutable immutable);
}

public class MyXmlSerializer<T>
{
    private readonly XmlSerializer _xmlSerializer;

    public MyXmlSerializer()
    {
        _xmlSerializer = new XmlSerializer(typeof(T));
    }

    public T Deserialize(string xml)
    {
        using (TextReader reader = new StringReader(xml))
        {
            return (T)_xmlSerializer.Deserialize(reader);
        }
    }

    public string Serialize(T obj)
    {
        var builder = new StringBuilder();
        using (TextWriter writer = new StringWriter(builder))
        {
            _xmlSerializer.Serialize(writer, obj);
        }
        
        return builder.ToString();
    }
}

This method is less generic as it will require you to create the mapping logic for each model type. You could simplify that process with something like AutoMapper, but I prefer to do this manually.

Tyler Harbert
  • 391
  • 1
  • 7
  • Unfortunately, I am not in control of XML format, and it uses a lot of attributes instead of elements (elements are for something else), so this solution is not an option. Thanks for the response anyway. – Adam Kaczmarski Dec 13 '21 at 11:46
  • Are you still working though this? I have another idea I could try if it could still benefit you. – Tyler Harbert Dec 14 '21 at 14:44
  • Yes, I am still working on this, and I will probably be working on it for a while, there is some work to do. – Adam Kaczmarski Dec 15 '21 at 12:13
  • I have added an update to a layered approach that works with System.Xml.Serialization to convert between immutable and mutable objects. I attempted to make this more generic by using inheritance but the XML serializer didn't seem to like anything I tried. – Tyler Harbert Dec 16 '21 at 13:12