In this post, we are going to learn about serializing objects to XML in C#.
So, what is XML Serialization? It is the transformation of the public fields and property values of an object (without type information) into an XML stream.
After the main definition, we can continue to see how to serialize objects to XML with several examples.
How to Serialize a Simple Object to XML in C#
For serializing objects to XML in C#, we can use the XmlSerializer
class from the System.Xml.Serialization
namespace. We are going to start with the serialization of a simple C# class:
public class Patient { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } public int RoomNo { get; set; } }
The Patient
class contains the necessary information for a hospital patient.
Next, let’s create a new object of type Patient
. We are going to serialize it into an XML file using the XmlSerializer
class:
var patient = new Patient() { ID = 232323, FirstName = "John", LastName = "Doe", Birthday = new DateTime(1990, 12, 30), RoomNo = 310 }; var serializer = new XmlSerializer(typeof(Patient)); using (var writer = new StreamWriter("patients.xml")) { serializer.Serialize(writer, patient); }
Here, we create an XMLSerializer
object that will serialize objects of type Patient
. The Serialize()
method transforms the object into XML. It also uses a StreamWriter
object to write the XML into a file.
The resulting XML file consists of an XML Root Element and all the information of the Patient
object:
<?xml version="1.0" encoding="utf-8"?> <Patient xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>232323</ID> <FirstName>John</FirstName> <LastName>Doe</LastName> <Birthday>1990-12-30T00:00:00</Birthday> <RoomNo>310</RoomNo> </Patient>
Note that the tags of the XML elements have exactly the same name as the properties of the Patient
object. We can change their names with the use of C# Attributes, as we will see later on.
Things to Note About Serialization in C#
There are some important details about XML serialization in C# that we should be aware of. First of all, every class that we want to serialize should define the default (parameterless) constructor. In our case, we have not defined any constructors. Therefore, the parameterless constructor is included by default.
Moreover, only the public members of a class will be serialized; private members will be omitted from the XML document.
The properties must have a public getter method. If they also have a setter method, this must be public too.
Serializing Classes with Nested Classes to XML
We can also serialize classes that contain objects from other classes.
Let’s introduce a new class that contains information about the patient’s home address:
public class Address { public string Street { get; set; } public string Zip { get; set; } public string City { get; set; } public string Country { get; set; } }
Next, we are going to modify the Patient
with a reference to Address
:
public class Patient { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } public int RoomNo { get; set; } public Address HomeAddress { get; set; } }
To serialize the patient
object, we are going to use the XmlSerializer
class:
var patient = new Patient() { ID = 232323, FirstName = "John", LastName = "Doe", Birthday = new DateTime(1990, 12, 30), RoomNo = 310, HomeAddress = new Address() { Street = "12 Main str.", Zip = "23322", City = "Boston", Country = "USA", } }; var serializer = new XmlSerializer(typeof(Patient)); using (var writer = new StreamWriter("patients.xml")) { serializer.Serialize(writer, patient); }
The resulting XML is going to have an additional element with the patient’s address details:
<?xml version="1.0" encoding="utf-8"?> <Patient xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>232323</ID> <FirstName>John</FirstName> <LastName>Doe</LastName> <Birthday>1990-12-30T00:00:00</Birthday> <RoomNo>310</RoomNo> <HomeAddress> <Street>12 Main str.</Street> <Zip>23322</Zip> <City>Boston</City> <Country>USA</Country> </HomeAddress> </Patient>
An Array of Objects Serialization
In order to serialize an array of objects, we have to instruct XmlSerializer
to expect objects of type Patient[]
:
var arr = new Patient[] { new Patient() { ... }, new Patient() { ... } }; var arrSerializer = new XmlSerializer(typeof(Patient[])); using (var writer = new StreamWriter("patients.xml")) { arrSerializer.Serialize(writer, arr); }
The resulting XML now contains a Root Element with the name ArrayOfPatient
:
<?xml version="1.0" encoding="utf-8"?> <ArrayOfPatient xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Patient> ... </Patient> <Patient> ... </Patient> </ArrayOfPatient>
Serializing Classes with Lists of Objects to XML
The XmlSerializer
class can also serialize classes that contain lists (or arrays) of objects:
public class Patient { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } public int RoomNo { get; set; } public Address HomeAddress { get; set; } public List<Measurement> Measurements {get; set;} }
Each Measurement
stores temperature readings for the patient:
public class Measurement { public DateTime TimeTaken { get; set; } public decimal Temp { get; set; } }
We can serialize a Patient
object in the same way as before:
var patient = new Patient() { ID = 232323, ... HomeAddress = new Address() { Street = "12 Main str.", ... }, Measurements = new List<Measurement>() { new Measurement() { TimeTaken = new DateTime(2022, 1, 1, 17, 50, 0), Temp = 38.4M }, new Measurement() { TimeTaken = new DateTime(2022, 1, 1, 22, 00, 0), Temp = 39.1M }, new Measurement() { TimeTaken = new DateTime(2022, 1, 2, 6, 30, 0), Temp = 37.3M } } }; var serializer = new XmlSerializer(typeof(Patient)); using (var writer = new StreamWriter("patients.xml")) { serializer.Serialize(writer, patient); }
Our XML file will now have an array of elements with the corresponding measurements:
<?xml version="1.0" encoding="utf-8"?> <Patient xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> .... <Measurements> <Measurement> <TimeTaken>2022-01-01T17:50:00</TimeTaken> <Temp>38.4</Temp> </Measurement> <Measurement> <TimeTaken>2022-01-01T22:00:00</TimeTaken> <Temp>39.1</Temp> </Measurement> <Measurement> <TimeTaken>2022-01-02T06:30:00</TimeTaken> <Temp>37.3</Temp> </Measurement> </Measurements> </Patient>
Controlling XML Serialization in C#
We can influence the serialization of an object with the use of C# Attributes. In this way, we can serialize an object property as an XML attribute (instead of an XML element). Moreover, we can select a different name for an element tag. We can also prevent a property from appearing in the resulting XML.
So, let’s start with the Patient
class modification by adding the required attributes:
[XmlRoot("PatientInfo")] public class Patient { [XmlAttribute ("PatientID")] public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [XmlIgnore] public DateTime Birthday { get; set; } [XmlElement("RoomNumber")] public int RoomNo { get; set; } public Address HomeAddress { get; set; } [XmlArray("TemperatureReadings")] [XmlArrayItem("TemperatureReading")] public List<Measurement> Measurements {get; set;} }
First of all, we can change the name of the Root Element from Patient
to PatientInfo
, using of XmlRoot
annotation.
Next, we choose to serialize the ID
property as an XmlAttribute
, under the name PatientID
.
The XmlIgnore
annotation prevents a property from appearing in the XML. Here, we use it to omit the patient’s birthday from the resulting file.
Furthermore, when our object contains a list – or an array – of objects, we can use the XmlArray
and XmlArrayItem
attributes to modify the way they will be serialized. The XmlArray
attribute changes the array’s tag name from Measurements
to TemperatureReadings
. Also, XmlArrayItem
provides a new name for each element of the Measurements
array.
Finally, let’s add the XmlText
attribute to the Address
class:
public class Address { public string Street { get; set; } public string Zip { get; set; } public string City { get; set; } [XmlText] public string Country { get; set; } }
With the use of XmlText
, we are going to serialize the Country
property as plain text inside the Address element.
As a result, we will have a different XML file:
<?xml version="1.0" encoding="utf-8"?> <PatientInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" PatientID="232323"> <FirstName>John</FirstName> <LastName>Doe</LastName> <RoomNumber>310</RoomNumber> <HomeAddress> <Street>12 Main str.</Street> <Zip>23322</Zip> <City>Boston</City> USA </HomeAddress> <TemperatureReadings> <TemperatureReading> <TimeTaken>2022-01-01T17:50:00</TimeTaken> <Temp>38.4</Temp> </TemperatureReading> <TemperatureReading> <TimeTaken>2022-01-01T22:00:00</TimeTaken> <Temp>39.1</Temp> </TemperatureReading> <TemperatureReading> <TimeTaken>2022-01-02T06:30:00</TimeTaken> <Temp>37.3</Temp> </TemperatureReading> </TemperatureReadings> </PatientInfo>
Handling Derived Classes
A special case of the list (or array) serialization arises when a list contains references to objects of derived classes. This usually happens with polymorphism. Let’s suppose we have a Person
class:
public class Person { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } }
This class serves as the base for the creation of the Patient
class:
public class Patient: Person { public int RoomNo { get; set; } }
And the Doctor
class:
public class Doctor: Person { public string Specialization { get; set; } }
Now, let’s create a list that contains references to objects of both types:
var people = new List<Person>() { new Doctor() { ID = 777, FirstName = "Jane", LastName = "Doe", Birthday = new DateTime(1975, 3, 5), Specialization = "Cardiologist" }, new Patient() { ID = 888, FirstName = "John", LastName = "Doe", Birthday = new DateTime(1980, 3, 21), RoomNo = 301 } }; var serializer = new XmlSerializer(typeof(List<Person>)); using (TextWriter writer = new StreamWriter("patients.xml")) { serializer.Serialize(writer, people); }
If we try to serialize this list, we will get an InvalidOperationException
:
InvalidOperationException: The type NamsepaceXXX.Doctor was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
As the exception detail hints, we should specify the derived types using the XmlInclude
attribute:
[XmlInclude(typeof(Doctor))] [XmlInclude(typeof(Patient))] public class Person { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } }
In this case, the serialization completes successfully. The resulting XML contains both objects with their respective content:
<?xml version="1.0" encoding="utf-8"?> <ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Person xsi:type="Doctor"> <ID>777</ID> <FirstName>Jane</FirstName> <LastName>Doe</LastName> <Birthday>1975-03-05T00:00:00</Birthday> <Specialization>Cardiologist</Specialization> </Person> <Person xsi:type="Patient"> <ID>888</ID> <FirstName>John</FirstName> <LastName>Doe</LastName> <Birthday>1980-03-21T00:00:00</Birthday> <RoomNo>301</RoomNo> </Person> </ArrayOfPerson>
Conclusion
In this post, we have seen how to perform XML serialization in C#. More specifically, we have learned to serialize simple and complex objects, as well as arrays and lists of objects. Finally, we have learned how to alter the structure of the resulting XML file with the use of attributes.