XML Serialization: Using XML files to persist data
Something that’s pretty common is to load and export data from applications to settings or data files. I’m going to be concentrating on the scenario in which you have some sort of data structure that you want to save or load the whole contents of the structure to an XML file. There are several methods to do this:
1) Using DataSets and their WriteToXML and ReadXML methods.
o Pros: This is the simplest option because all you have to do is define a DataSet (which you can easily do through the designer) and initialize it on your code. There’s no need to write any additional code.
o Cons: You’re restricted to using a DataTables and DataRows instead of something more customizable
2) Writing custom read and write methods using the classes from the System.Xml namespace
o Pros: When you want to have full control of how you’re parsing the data, what to do about error recovery, extra validation, etc. This is great because you have absolute control over what you’re doing.
o Cons: Since you do have absolute control over what you’re doing you also have to micro manage. This is simply overkill when you want something simple.
3) Using the System.Xml.Serialization namespace
o Pros: It’s the right balance between too much and too little control. It’s simple to setup and use.
o Cons: You don’t have as much control on things such as error recovery.
We’re going to concentrate on how to do this using the third method.
The Basics
We created a console app with two methods ReadFile and WriteFile (I’ll go into details for those in the next section). Our code looks like this:
using System; using System.IO; using System.Xml; using System.Xml.Serialization; namespace SerializationTest { public class MyClass { public String PropertyOne { get; set; } public String PropertyTwo { get; set; } public MyClass (String propertyOne, String propertyTwo) { PropertyOne = propertyOne; PropertyTwo = propertyTwo; } public override string ToString() { return String.Format("PropertyOne: {0}\nPropertyTwo: {1}", PropertyOne, PropertyTwo); } } class Program { static void Main(string[] args) { String filename = "MyClass.xml"; MyClass myClass = new MyClass("One", "Two"); WriteFile(filename, myClass); MyClass myClass2; ReadFile(filename, out myClass2); Console.WriteLine(myClass2.ToString()); } } } |
There are two things we need to add to make that class serializable:
1) Add the [Serializable] attribute to the class declarations
[Serializable]
public class MyClass
2) Declare a default parameterless constructor. Without this you’ll get an InvalidOperationException on the Serializer initialization.
public MyClass() { }
Once that’s done you can successfully run the program and this xml file will be generated:
<?xml version="1.0" encoding="utf-8"?> <MyClass xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"> <PropertyOne>One</PropertyOne> <PropertyTwo>Two</PropertyTwo> </MyClass> |
So if you simply want the most basic serialization that’s all that’s needed.
The serialization and deserialization calls
So what exactly it’s on the ReadFile and the WriteFile methods that we previously black boxed? It’s much simpler than what you would think. They initialize a Stream, a Serializer and then do the pertinent Serialize or Deserialize calls:
static void WriteFile(String filename, MyClass myClass) { XmlSerializer serializer = new XmlSerializer(typeof(MyClass)); try { using (StreamWriter streamWriter = new StreamWriter(filename)) { try { serializer.Serialize(streamWriter, myClass); } catch(Exception e) { // Exception caught while serializing the class // TODO: Add your exception handler here } finally { streamWriter.Close(); } } } catch (Exception e) { // Exception caught writing xml file // TODO: Add your exception handler here } } static MyClass ReadFile(String filename) { XmlSerializer serializer = new XmlSerializer(typeof(MyClass)); try { MyClass myClass = new MyClass(); using (StreamReader streamReader = new StreamReader(filename)) { try { myClass = (MyClass)serializer.Deserialize(streamReader); } catch(Exception e) { // Exception caught while deserializing the class file // TODO: Add your exception handler here } finally { streamReader.Close(); } } return myClass; } catch (Exception e) { // Exception caught reading xml file // TODO: Add your exception handler here } } |
Adding arrays to the data structure
You can also add arrays to your structure and they’ll get correctly handled through serialization. Let’s add an int array to MyClass (along with the appropriate changes in the constructor, ToString method and the initialization of myClass on Main).
public int[] Parameters { get; set; } |
After rerunning our program the xml looks like this:
<?xml version="1.0" encoding="utf-8"?> <MyClass xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"> <PropertyOne>One</PropertyOne> <PropertyTwo>Two</PropertyTwo> <Parameters> <int>1</int> <int>2</int> </Parameters> </MyClass> |
Renaming the elements on the XML file
What if you want the xml to show up with different names than the names of your data structure but you don’t really want to rename your properties or your class itself? The answer to that is using the XmlElement and XmlRoot tags. Say for example that for this case your class is going to be representing some kind of measures so PropertyOne represents height while PropertyTwo represents weight and parameters mean extra measures. We only need to add the appropriate Xml tags on top of each declaration. Note that the array declarations can have two tags, the XmlArray will control the name of the parent tag and the XmlArrayItem will control the individual item tags.
[XmlRoot("Measures")] public class MyClass [XmlElement("Height")] public String PropertyOne { get; set; } [XmlElement("Weight")] public String PropertyTwo { get; set; } [XmlArrayItem("Value")] [XmlArray("AdditionalMeasures")] public int[] Parameters { get; set; } |
And the resulting xml file:
<?xml version="1.0" encoding="utf-8"?> <Measures xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"> <Height>One</Height> <Weight>Two</Weight> <AdditionalMeasures> <Value>1</Value> <Value>2</Value> </AdditionalMeasures> </Measures> |
Serializing a list of elements
So we’ve “mastered” how to load and export data from a single object through xml serialization. What if you want to do the same with a list of objects? Since there’s a one to one mapping for xml we need to create a “container” class that has an array of the objects we want to serialize and use an instance of this class instead for your serialization operations.
[Serializable] [XmlRoot("MeasuresList")] public class MyClassContainer { [XmlElement("Measures")] public MyClass[] MyClasses { get; set; } public MyClassContainer () { } public MyClassContainer (MyClass[] myClasses) { MyClasses = myClasses; } } |
In addition to these changes you would need to either write new Read/Write methods or change the existing ones to use MyClassContainer instead of MyClass.
static void WriteFile(String filename, MyClassContainer myClass) { XmlSerializer serializer = new XmlSerializer(typeof(MyClassContainer)); … } static MyClassContainer ReadFile(String filename) { XmlSerializer serializer = new XmlSerializer(typeof(MyClassContainer)); MyClassContainer myClass = new MyClassContainer(); … myClass = (MyClassContainer)serializer.Deserialize(streamReader); … } |
Our final xml file would look like this:
<?xml version="1.0" encoding="utf-8"?> <MeasuresList xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"> <Measures> <Height>One</Height> <Weight>Two</Weight> <AdditionalMeasures> <Value>1</Value> <Value>2</Value> </AdditionalMeasures> </Measures> <Measures> <Height>Three</Height> <Weight>Four</Weight> <AdditionalMeasures> <Value>1</Value> <Value>2</Value> </AdditionalMeasures> </Measures> </MeasuresList> |
On a final note, if you rather work with Collections instead of arrays you can simply add a couple of overloads to the container class. First add constructor that takes a List (or whatever collection you prefer) and finally a method that converts the array to your preferred collection type.
public MyClassContainer (List<MyClass> myClasses) { MyClasses = myClasses.ToArray(); }
public List<MyClass> ToList() { List<MyClass> classList = new List<MyClass> (); for (int i = 0; i < MyClasses.Length; i++) classList.Add(MyClasses[i]); return classList; } |