XmlSerializer vs. DataContractSerializer – why the latter is better?

0
313
XML Serializers

I like the idea that one should write C# code as much functional as possible. Sometimes it’s difficult, sometimes even impossible, but on the other hand, we have some low hanging fruits that can be reached easily. One of such fruits is immutable classes. This post shows how DataContractSerializer and XMLSerializer differs and how to utilize DCS ability to serialize private members in order to have cleaner POCO classes.

This entry was inspired by one of my colleagues. While reviewing his code I mentioned that instead of an object initializer he could use a custom constructor and made properties read-only. He replied that it’s difficult, as then it would be impossible to serialize it. And he was right. Partially, because XMLSerializer has such limitations. But if we want to send objects of this class through WCF,  DataContractSerializer will be used. I decided then to explain the differences between those two serializers.

What will be shown

I would like to show a few variations of the simple class „Pet” containing only one string property „Name”. I will try to serialize it, then deserialize (which is basically a cloning operation) and compare the original object with its processed copy. Test framework is nUnit.

Class variations to be tested:

  1. Pet_Public_Default_Ctor_And_Property – with public default ctor and public property with getter/setter
  2. Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property – with public default ctor, public custom ctor and public property
  3. Pet_Public_Custom_Ctor_And_Public_Property – without default ctor, with public custom ctor and public property
  4. Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property – with private default ctor, public custom ctor and public property
  5. Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property – with private default ctor, public custom ctor and property with private setter

The above cover probably the most important cases used by us. From now on I will use numbers as references to these classes.

XmlSerializer tests

XmlSerializerTests contain all mentioned classes, CloneBySerialization method and test for each class.

using System.IO;
using System.Text;
using System.Xml.Serialization;
using NUnit.Framework;

namespace MyPoc
{
    [TestFixture]
    public class XmlSerializerTests
    {
        private T CloneBySerialization<T>(T obj)
        {
            var xmlSerializer = new XmlSerializer(typeof(T));
            var stringBuilder = new StringBuilder();
            using (var writer = new StringWriter(stringBuilder))
            {
                xmlSerializer.Serialize(writer, obj);
            }

            using (var reader = new StringReader(stringBuilder.ToString()))
            {
                T clonedObject = (T)xmlSerializer.Deserialize(reader);
                return clonedObject;
            }
        }

        public class Pet_Public_Default_Ctor_And_Property
        {
            public string Name { get; set; }

            public Pet_Public_Default_Ctor_And_Property()
            {
            }
        }

        [Test]
        public void Class_With_Default_Ctor_And_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Default_Ctor_And_Property {Name = "Mickey"};
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        public class Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property
        {
            public string Name { get; set; }

            public Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property()
            {
            }

            public Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property(string name)
            {
                Name = name;
            }
        }

        [Test]
        public void Class_With_Custom_Ctor_And_Public_Ctor_And_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }


        public class Pet_Public_Custom_Ctor_And_Public_Property
        {
            public string Name { get; set; }

            public Pet_Public_Custom_Ctor_And_Public_Property(string name)
            {
                Name = name;
            }
        }

        [Test]
        public void Class_With_Custom_Ctor_Without_Default_With_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        public class Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property
        {
            public string Name { get; set; }

            private Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property()
            {
            }

            public Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property(string name)
            {
                Name = name;
            }
        }

        [Test]
        public void Class_With_Custom_Ctor_With_Private_Default_With_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        public class Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property
        {
            public string Name { get; private set; }

            private Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property()
            {
            }

            public Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property(string name)
            {
                Name = name;
            }
        }

        [Test]
        public void Class_With_Custom_Ctor_With_Private_Default_With_Private_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }
    }
}

Test results:

Class Success Error
1 YES
2 YES
3 NO Cannot deserialize type because it contains property 'Name' which has no public setter.
4 YES
5 NO The class cannot be serialized because it does not have a parameterless constructor.

 

It means that XmlSerializer can serialize only classes with default ctor defined. It may be private but must exist. This is OK for our purposes. But the showstopper is that it cannot process properties other than public. 

DataContractSerializer tests

The concept is the same. Test class has been slightly changed, as DataContractSerializer requires slightly different code to be run.

using System.IO;
using System.Runtime.Serialization;
using System.Text;
using NUnit.Framework;

namespace MyPoc
{
    [TestFixture]
    public class DataContractSerializerTests
    {
        private T CloneBySerialization<T>(T obj)
        {
            string xml;
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (StreamReader reader = new StreamReader(memoryStream))
                {
                    DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
                    serializer.WriteObject(memoryStream, obj);
                    memoryStream.Position = 0;
                    xml = reader.ReadToEnd();
                }
            }

            using (MemoryStream memoryStream = new MemoryStream())
            {
                byte[] data = Encoding.UTF8.GetBytes(xml);
                memoryStream.Write(data, 0, data.Length);
                memoryStream.Position = 0;
                DataContractSerializer deserializer = new DataContractSerializer(typeof(T));
                return (T)deserializer.ReadObject(memoryStream);
            }
        }

        [Test]
        public void Class_With_Default_Ctor_And_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Default_Ctor_And_Property { Name = "Mickey" };
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        [Test]
        public void Class_With_Custom_Ctor_And_Public_Ctor_And_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Default_Ctor_Custom_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        [Test]
        public void Class_With_Custom_Ctor_Without_Default_With_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }

        [Test]
        public void Class_With_Custom_Ctor_With_Private_Default_With_Public_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Public_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }
    
        [Test]
        public void Class_With_Custom_Ctor_With_Private_Default_With_Private_Properties_Can_Be_Cloned()
        {
            var pet = new Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property("Mickey");
            var clonedPet = CloneBySerialization(pet);
            Assert.AreEqual(pet.Name, clonedPet.Name, $"Name should be: {pet.Name}, but is: {clonedPet.Name}");
        }
    }
}

Now results look like:

Class Success Error
1 YES
2 YES
3 NO Type cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute.
4 YES
5 NO Failed: Name should be: Mickey, but is:
Expected: „Mickey”
But was: null

I expected better results. The same tests failed, but with different error messages. Fortunately first message tells us what to do. DataContractSerializer requires two attributes in this case. Exemplary class will be look like:

 [DataContract]
        public class Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property
        {
            [DataMember]
            public string Name { get; private set; }

            public Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property(string name)
            {
                Name = name;
            }
        }

And that’s exactly what we wanted. The serialized object is valid XML that differs slightly from XmlSerializer output:

From DataContractSerializer:
<DataContractSerializerTests.Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property xmlns="http://schemas.datacontract.org/2004/07/MyPoc" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>Mickey</Name>
</DataContractSerializerTests.Pet_Public_Custom_Ctor_And_Private_Default_Ctor_And_Private_Property>

From XmlSerializer:
<?xml version="1.0" encoding="utf-16"?>
<Pet_Public_Default_Ctor_And_Property xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>Mickey</Name>
</Pet_Public_Default_Ctor_And_Property>

But it’s not obvious which version is better: more readable for humans or for the machines – here is a short article on white spaces in XML documents.

Which serializer choose?

This article is not about choosing a serializer. Both have different behaviours:

XmlSerializer DataContractSerializer
Serializes all public members by default (opt-out behaviour), one has to mark property with XmlIgnore attribute to skip serialization. Serializes only properties marked with DataMember attribute (opt-in behaviour).
Cannot serialize private members, only public properties with getter and setter. Can serialize private members (great for us in the context of this post)
Doesn’t care about XML elements order Requires XML elements in the same order as properties in the class (very troublesome!)
Very customizable, output XML may be easily changed using attributes like XmlElemet, XmlArrayItem etc. Doesn’t support such attributes
Slightly slower, as it cares about decorating attributes Slightly faster, as it’s optimized and doesn’t care about XML attributes mentioned above.
Uses XmlInclude attribute to recognize additional types Uses KnownTypes attribute to recognize additional types – probably gives more control over that process
Doesn’t understand DataContract attribute used by its opponent. Recognises Serializable attribute and knows that the class should be serialized
Cannot serialize Dictionary Can serialize Dictionaries and other useful types
Used by ASMX Used by WCF (can be changed with XmlSerializerFormat and DataContractFormat attributes)
Works with XML Works with XML and JSON

 

In short, we can tell that if we want to have more control over XML format because it will be used by humans, then we should choose XmlSerializer. Otherwise – DataContractSerializer.

What to remember from this post?

Question from the title – why DataContractSerializer is better? It’s not better at all. It just serves different purposes and if we use it we should take the best from it. That’s why this post was created.

Writing it I also wanted to remember that if DataContractSerializer is used, we can make our code slightly cleaner, utilising immutable POCO classes. Hopefully, the careful reader will learn more facts useful for them.

 


Warning: A non-numeric value encountered in /home/platne/serwer17076/public_html/wp-content/themes/Newspaper/includes/wp_booster/td_block.php on line 1008