Coverage Topic
- Basic idea about de-serialization and serialization
- Advantage and disadvantages of xml Serialization
- Implementation of xml serialization
- White Box testing of xml serialization
- Advantage and disadvantages of data-contract-serialization
- Problem Analysis
- Implementation of sortable data-contract serialization
- White-Box testing of data-contract serialization
Let's Drill Down the Basic Concept
The above diagram wants to say,
- Deserialization: Transforming XML string to object.
- Serialization: Transforming object to XML string.
XmlSerializer
- Only Public Properties can be transformed.
- It can serialize collections which can be inherited from IEnumerable and ICollection.
- You don’t need to specify serialization attributes for every property that needs to be transformed. If you want to ignore to translate any property, then you have to use [XmlIgnore] attribute.
Limitation
- It can serialize only property
- Properties should have the set and get functionality. It can’t serialize private or read only properties, indexers and methods.
Class Diagram
Project CreationThe problem can be solved many different ways. Right now, I am mainly focusing only on Xml Serialization and Data-Contract Serialization.
Basic ConceptI have used the following references into the project for the XML and data-contract serialization:
- System.Xml.Serialization
- System.Runtime.Serialization
I have also used System.IO namespace. We need the classes which are given bellow:
- MemoryStrean
- StreamWriter
- StreamReader
Memory Stream Class
- It stores data in memory (RAM)
- It is used for fast and temporary storage
StreamWriter and StreamReader
These both of the classes are used for reading and writing character based data.
Flush with a StreamWriter
Flush is used to move information buffer to destination.
Implementation of Xml Serializer
There is no doubt that these codes are okay. It can serialize the object to xml string. You can use these codes without any problems.
Now let’s see another implementation with using block.
GetType vs. typeof:
Look at the above image, I have used the using block and these are not the major change in this implementation. Look at the no.1 (marked with red), I have used tag.GetType() instead of typeof(T). It is better to use GetType because of the code safety.
In the first implementation of the image, when I am going to call serializer.Serialize(streamWriter, tag), sometimes XmlSerializer throws a runtime exception because of the typeof(T).
If you don’t get the error, because of using the typeof(T), then you can have different opinions. Anyway, I'm going to use GetType instead of typeof(T).
Benefits
- Because of the using block, dispose methods are automatically called. You don't need to call it manually.
- Even if, I did not use StreamWriter.Flush (No.2 in the codes of the first implementation).
Implementation of XML DeSerializer
Look at the above image; I have changed very silly things. You can use either one of the implementation. So, you can use these deserialization method for transforming xml string to object.
White Box Testing of XML Serializer/De-Serializer
For the white-box testing, I am going to use Behavior Driven Development (BDD) for the naming convention of the test method.
Concept of BDD
- Given I am a beginner to the BDD technique, and I never use this technique before
- When I read this tutorial for BDD
- Then I have started to like it and finally I learn it.
Note
I'm avoiding the details of the BDD to keep this simple.
XML DeSerializer TestingI am writing a method to test the DeSerializer method.
Naming Convention of a Test Method
- Given_Valid_XML_String_For_Person_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Person_Object()
Say, I have an xml string which has the name and address of a person.
- <?xml version='1.0' encoding='utf-16'?>
- <Person>
- <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId>
- <LastName>Rony</LastName>
- <FirstName>HR</FirstName>
- <Address>
- <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId>
- <City>City</City>
- <State>State</State>
- <ZipCode>1200</ZipCode>
- </Address>
- </Person>
The full test method is given bellow and it should be passed.
-
-
-
-
- [TestMethod]
- public void Given_Valid_XML_String_For_Person_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Person_Object()
- {
-
- Person personObj;
-
-
- string xmlData =
- @"<?xml version='1.0' encoding='utf-16'?>
- <Person>
- <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId>
- <LastName>Rony</LastName>
- <FirstName>HR</FirstName>
- <Address>
- <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId>
- <City>City</City>
- <State>State</State>
- <ZipCode>1200</ZipCode>
- </Address>
- </Person>";
-
- string expectedLastNamet = "Rony";
-
-
- personObj = xmlSerializerUtility.DeSerializer<Person>(xmlData);
-
-
- Assert.AreEqual(personObj.LastName, expectedLastNamet);
- }
XML Serializer TestingThe serialize method is called inside the test method and it should be passed.
- [TestMethod]
- public void Given_Valid_PersonObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data()
- {
-
- Address address = new Address
- {
- AddressId = Guid.NewGuid(),
- City = "City",
- State = "State",
- ZipCode = "1200"
- };
-
- Person person = new Person
- {
- PersonId = Guid.NewGuid(),
- FirstName = "HR",
- LastName = "Rony",
- Address = address
- };
-
-
- string actualXmlData = null;
-
-
- actualXmlData = xmlSerializerUtility.Serializer<Person>(person);
-
-
- Assert.IsNotNull(actualXmlData);
- }
The ProblemI’m implementing a micro service. Say, there is a client interface which will send data as xml format to the service. I have no control over the client interface. Somehow, I have figured out the elements/properties which I need for the model (entity) classes. But I have no control to change the order of the properties. After analyzing the xml as well as the entity classes, I have got the following issues,
- Service will receive XML string which has un-sorted ordered elements.
- Each time, it would come from different sources with different orders.
- Existing model class contains IDictionary or IList.
I am focusing only on XML and Data Contract De-Serializer/Serializer.
DataContractSerializer
- Public Properties and private fields can be transformed. But you have to mention [DataMember] attribute.
- It doesn’t support xml attributes
- It supports IList, IDictionary (HashTable) interface.
Limitation
- XML should have to be alphabetically sorted.
Performance usually, it is 10% faster than XmlSerializer.
Implementation of DataContract Serializer
Look at the above codes, it is almost similar to the previous implementation except DataContractSerializer Class.
Now say, I have xml string which has the name and education properties of an Employee.
- <Employee>
- <LastName>Rony</LastName>
- <FirstName>HR</FirstName>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- <Educations>
- <Education>
- <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- <DegreeText>Bachelors in computer science</DegreeText>
- </Education>
- <Education>
- <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- <DegreeText>Masters in computer science</DegreeText>
- </Education>
- </Educations>
- </Employee>
Look at the tag of the elements (LastName, FirstName, EmployeeId) in the xml strings which are not alphabetically ordered.
Problem Analysis Again, if you look at the model class of the employee, then you will see that, at runtime, it is alphabetically ordered.
So, if I run the test, then I will get the invalid data in the object.
Look at the above object, it just gets the value of "LastName". Because in the employee model class, first it gets the LastName before the FastName. So, it takes the value of the LastName from the xml and after that it ignores values of the FirstName and others.
Solutions
- Sort the XML elements.
- Remove the empty tags too.
I have added two extension methods for sorting and removing empty elements of the XML string.
- internal static class XmlSorter
- {
-
-
-
-
-
- internal static void Sort(this XElement xElement, bool sortAttributes = true)
- {
-
- if (xElement == null) throw new ArgumentNullException("XElement is null");
-
-
- if (sortAttributes)
- {
- List<XAttribute> sortedAttributes = xElement.Attributes().OrderBy(a => a.ToString(), new CaseSensitiveComparer()).ToList();
- sortedAttributes.ForEach(a => a.Remove());
- sortedAttributes.ForEach(a => xElement.Add(a));
- }
-
-
- List<XElement> sortedChildren = xElement.Elements().OrderBy(e => e.Name.ToString(), new CaseSensitiveComparer()).ToList();
- if (xElement.HasElements)
- {
- xElement.RemoveNodes();
- sortedChildren.ForEach(c => c.Sort(sortAttributes));
- sortedChildren.ForEach(c => xElement.Add(c));
- }
- }
-
- internal static void RemoveEmptyElement(this XElement xElement)
- {
-
- if (xElement == null) throw new ArgumentNullException("XElement is null");
-
-
- xElement.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
- }
- }
-
-
-
-
-
- internal class CaseSensitiveComparer : IComparer<string>
- {
- public int Compare(string x, string y)
- {
- return string.Compare(x, y, StringComparison.Ordinal);
- }
- }
Sortable DataContract Serializer
Look at the above codes, I have wrapped the previous implementation of the DataContractSerializerUtility class. I have called RemoveEmptyElements extension method to remove the empty tags from the xml string.
After removing the empty tags, it sorts the tags of the xml strings by calling the sort extension method.
White Box Testing of DataContract Serializer/De-Serializer
Now if I run the following test, then it will be passed the test. Now these methods are ready to use.
Deserialization Testing
-
-
-
-
- [TestMethod]
- public void Given_Valid_XML_Data_For_Employee_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Employee_Object()
- {
-
- ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer);
-
- Employee employeeObj;
-
- string xmlData = @"
- <Employee>
- <LastName>Rony</LastName>
- <FirstName>HR</FirstName>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- <Educations>
- <Education>
- <DegreeText>Bachelors in computer science</DegreeText>
- <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- </Education>
- <Education>
- <DegreeText>Masters in computer science</DegreeText>
- <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId>
- <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
- </Education>
- </Educations>
- </Employee>";
-
- int expectedCount = 2;
-
-
- employeeObj = sortabledataContractSerializer.DeSerializer<Employee>(xmlData);
-
-
- Assert.AreEqual(employeeObj.Educations.Count, expectedCount);
- }
The expected result from the test is given below,
Serialization Testing
-
-
-
-
- [TestMethod]
- public void Given_Valid_EmployeeObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data()
- {
-
- ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer);
-
-
- Employee employee = new Employee();
- employee.EmployeeId = Guid.NewGuid();
- employee.FirstName = "HR";
- employee.LastName = "Rony";
-
- List<Education> educationList = new List<Education>();
- Education education = new Education();
- education.EducationId = Guid.NewGuid();
- education.EmployeeId = employee.EmployeeId;
- education.DegreeText = "Bachelors in computer science";
- educationList.Add(education);
-
- Education educationObj2 = new Education();
- educationObj2.EducationId = Guid.NewGuid();
- educationObj2.EmployeeId = employee.EmployeeId;
- educationObj2.DegreeText = "Masters in computer science";
- educationList.Add(educationObj2);
- employee.Educations = educationList;
-
-
- string actualXmlData = null;
-
-
- actualXmlData = sortabledataContractSerializer.Serializer<Employee>(employee);
-
-
- Assert.IsNotNull(actualXmlData);
- }
I have tried to show you, how to find out the problem as well as how to fix it quickly. Find the source code and full project. It is attached.