Serializing/Deserializing Objects Using XML

Occasionally you run across the need to store object information to persistent storage.  Many times that persistent storage takes the form of a database.  However, not all applications make use of databases.  In these cases, persistent storage is usually accomplished using the file system.

There are multiple methods for storing object information on the file system, each of which has different levels of built-in support in the .NET Framework.  One method is to use .NET’s configuration API (app.config, web.config).  Although this API provides a great deal of built-in configuration support, configuration files are read-only and therefore may not be the right solution for your needs.

Another method is to write your own code to create, read, update, and delete either text or binary files using a format of your own choosing.  This will give you the most flexibility, but does so at the expense of leaving you to write and test more code.

A third method, and the one that is the topic of this post, is to use .NET’s XML API, specifically the XMLSerializer class.  Using this class, we can take a standard C# object and write (or serialize) it to the file system as an XML document.  We can also go in reverse, reading the XML document from the file system and generating an instance of the original object and its state.

I recently came across the need for such an approach while working on a game that I’m developing with my kids.  If you’ve played PC games, you know that most of them have a settings screen where you can re-map game commands to different keyboard keys and have those new mappings persist without having to set them each time the game is run.  This seemed like a perfect use case for the XMLSerializer class.

There are two basic tasks involved with serializing/deserializing objects using XML.  The first task is that we need to define a class, an instance of which we intend to serialize/deserialize.  In my case, I defined a KeyboardMappingsConfig class, as shown below.

    /// <summary>
    /// Provides object schema definition for serializing/deserializing keyboard mappings to/from configuration file.
    /// </summary>
    [XmlRoot("KeyboardMappings")]
    public class KeyboardMappingsConfig
    {
        /// <summary>
        /// Key mapped to the Escape control command.
        /// </summary>
        public Keys Escape { get; set; }

        /// <summary>
        /// Key mapped to the Console control command.
        /// </summary>
        public Keys Console { get; set; }

        /// <summary>
        /// Key mapped to the ListPlayers control command.
        /// </summary>
        public Keys ListPlayers { get; set; }

        /// <summary>
        /// Key mapped to the Forward control command.
        /// </summary>
        public Keys Forward { get; set; }

        /// <summary>
        /// Key mapped to the Back control command.
        /// </summary>
        public Keys Back { get; set; }

        /// <summary>
        /// Key mapped to the Left control command.
        /// </summary>
        public Keys Left { get; set; }

        /// <summary>
        /// Key mapped to the Right control command.
        /// </summary>
        public Keys Right { get; set; }

        /// <summary>
        /// Key mapped to the Up control command.
        /// </summary>
        public Keys Up { get; set; }

        /// <summary>
        /// Key mapped to the Down control command.
        /// </summary>
        public Keys Down { get; set; }
    }

As you can see, there’s nothing very special about this class.  Although my class doesn’t have any methods assigned to it, there is nothing preventing you from adding methods to it.  The main thing is that any properties that you want to read/write from/to XML must have public getters and setters.  This is because the XMLSerializer class scans the object to determine which values to write to XML via the getters and populates a new instance of the object when reading from the XML via the setters.  Notice also the XmlRoot attribute on the class declaration.  We’ll come back to that in a later post, but for now you should know that various attributes can be used on the class and property declarations to override the default behavior of how the XmlSerializer class serializes/deserializes.

Once we have the to-be-serialized class defined, the second task is to write the code to serialize/deserialize the class using the XMLSerializer, starting with the code shown below.

string settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyGame");
string keyboardMappingsConfigPath = Path.Combine(settingsFolder, "KeyboardMappings.xml");

if (Directory.Exists(settingsFolder) == false)
{
    Directory.CreateDirectory(settingsFolder);
}

XmlSerializer serializer = new XmlSerializer(typeof(KeyboardMappingsConfig));
using (StreamWriter writer = new StreamWriter(keyboardMappingsConfigPath))
{
    serializer.Serialize(writer.BaseStream, keyboardMappingsConfig);
}

The first few lines of code check to see if the folder that we’re using to store the XML files already exists, and create it if it doesn’t.  Further down, we create a XmlSerializer object, whose constructor takes the type of class to be serialized (KeyboardMappingsConfig in our case).  This allows the XmlSerializer object to know what type of class it is dealing with when serializing.  Next we create a StreamWriter (giving it the path to use) and call the XmlSerializer’s Serialize method using the StreamWriter and an instance of the KeyboardMappingsConfig class (previously instantiated and populated outside of this code section).  After this code runs, there should be a file called KeyboardMappings.xml on the file system at the specified location, the contents of which should look similar to that shown below.

<?xml version="1.0"?>
<KeyboardMappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Escape>Escape</Escape>
  <Console>T</Console>
  <ListPlayers>Tab</ListPlayers>
  <Forward>W</Forward>
  <Back>S</Back>
  <Left>A</Left>
  <Right>D</Right>
  <Up>Space</Up>
  <Down>LeftShift</Down>
</KeyboardMappings>

Now that we’ve serialized our object to XML, let’s try to deserialize it.  As you probably expect, the process is pretty much the reverse of what we did above, as shown below.

string keyboardMappingsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyGame", "KeyboardMappings.xml");
KeyboardMappingsConfig keyboardMappingsConfig = null;

XmlSerializer serializer = new XmlSerializer(typeof(KeyboardMappingsConfig));
if (File.Exists(keyboardMappingsConfigPath) == true)
{
    using (StreamReader reader = new StreamReader(keyboardMappingsConfigPath))
    {
        keyboardMappingsConfig = (KeyboardMappingsConfig)serializer.Deserialize(reader);
    }
}

Similar to before, the first line is for specifying the file path/name.  Next, we create a KeyboardMappingsConfig object reference (the class we defined earlier), followed by the creation of a XmlSerializer object, whose constructor (as before) takes the type of class to be deserialized.  Next, we check to see if the file already exists.  Assuming it does, we load it into a StreamReader and pass the StreamReader to the XmlSerializer’s Deserialize method, the result of which we need to cast to our KeyboardMappingsConfig class.  After this code runs, we should have an instance of the KeyboardMappings class whose values match those in the XML file.

Pretty darn simple!

That’s it for now.  In the next post, we’ll dig into some more advanced aspects of the XmlSerializer class using attributes to override and customize its default behavior.  Until then, happy serializing!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s