Page 1 of 1

Reading and Writing XML using Serialization

#1 Robin19  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 271
  • View blog
  • Posts: 550
  • Joined: 07-July 10

Posted 22 September 2010 - 07:47 AM

*
POPULAR

Introduction
Several people have problems using the XML class to load and save their data. It can get confusing when an XMLNode at one level looks and acts the same as an XMLNode at another level. I loved the idea of XML, but it always seemed more work than it was worth to get it to work correctly.

tlhIn'toq posted an XML solution for a poster in this thread. I saw that code and fell in love with it. It was simple. It was elegant. It did exactly what it needed to do. It used serialization to remove a lot of the grunt work involving XML. It was also built specifically for the poster's class. That's when I had the idea of using Generics to make the code more powerful. Ever since then XML files bend to my bidding. All it takes is a little work on the front end. I'll show you how to do it.



Set Up
First, we need some classes to work with. Since the original solution was for XNA, I stuck with the video game theme. But I wrote this without XNA installed, nor is this solution only for game programmers. I used a copy of VS2010, .Net 4.0 (but I'm sure it will work on earlier versions), all optimized for my work production.

First I created an enum called ItemType:
    public enum ItemType
    {
        Weapon,
        Shield,
        Potion,
    }


This is used by my two classes. The first is an Item:
    public class Item
    {
        #region Fields
        public ItemType Type;
        public int Strength;
        public String Name;
        #endregion

        #region Constructors
        #region Item()
        public Item()
        {
            Type = ItemType.Potion;
            Name = "";
            Strength = 0;
        }
        #endregion

        #region Item(Name, Type, Strength)
        public Item(string Name, ItemType Type, int Strength)
        {
            this.Name = Name;
            this.Type = Type;
            this.Strength = Strength;
        }
        #endregion

        #region Item(Item)
        public Item(Item old)
        {
            this.Name = old.Name;
            this.Type = old.Type;
            this.Strength = old.Strength;
        }
        #endregion
        #endregion
        
        #region ToString
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("\tName: ");
            builder.Append(Name);
            builder.Append("\n\tType: ");
            builder.Append(Type.ToString());
            builder.Append("\n\tStrength: ");
            builder.Append(Strength);
            return builder.ToString();
        }
        #endregion
    }


A very important note. Any class that uses this trick must have a default, parameterless constructor. It just does.

My second class is a Unit:
    public class Unit
    {
        #region Fields
        public Item Weapon;
        public Item Shield;
        public List<Item> Bag;
        public string Name;
        #endregion

        #region Constructors
        #region Unit()
        public Unit()
        {
            Bag = new List<Item>();
        }
        #endregion

        #region Unit(Name)
        public Unit(string Name) : this()
        {
            this.Name = Name;
        }
        #endregion
        #endregion
        
        #region ToString
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("Name: ");
            builder.Append(Name);
            builder.Append("\nWeapon:\n");
            builder.Append(Weapon);
            builder.Append("\nShield:\n");
            builder.Append(Shield);
            builder.Append("\nBag:\n");
            foreach (Item item in Bag)
            {
                builder.Append(item);
                builder.Append("\n\n");
            }
            return builder.ToString();
        }
        #endregion
    }


Another note. This project was created for testing purposes. I created a couple of public fields and some basic constructors. Production code should use private fields, properties, better constructors (but at least a default one!), and methods. I left them out for space consideration. I added the ToString method for later use.



Serialization
This is the bit of front end you need to do to get the code to work. This is easy work, though. If you know how to serialize already, you can skip this section.

What we want is a way for the code to take apart the class and recombine it. This can be used for many things. It is used to write with BinaryWriter to a file. It's also used a lot when working with ports. Your class needs a way of breaking itself down, and building itself from those parts. First, you need to add the using System.Runtime.Serialization to both the Item and the Unit classes (or just once if you have them in the same file).

Let's break down our Item class. The class has three properties, a number of constructors, ToString, and any othe methods you wanted. The important bits we need are the properties. The other information can be recreated if we have this information. We'll create a method to pry this information out. The method name will be GetObjectData. It will return void. It will take two parameters, SerializationInfo and StreamingContext. This signature needs to be exact, so the serializers can call it.
        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
        }


In the method, we want to give the SerializationInfo any info we need to serialize. SerializationInfo has a method called AddValue that will help us do that. We pass two parameters, the name of the property and the value. So add the following to GetObjectData:
            info.AddValue("Name", Name);
            info.AddValue("Type", Type);
            info.AddValue("Strength", Strength);


It doesn't matter what names we give the properties, but it is easier to debug if you give it a simple name to remember and type.

You are done with the first part. You took apart an Item. Now you just need to put it back together. For this, we need a constructor. It will have the same properties as GetObjectData.
        public Item(SerializationInfo info, StreamingContext ctxt)
        {
        }


The SerializationInfo is the same object that we just passed our properties. We need to get them back. We do this by calling the GetValue method. This method also takes two parameters, the name of the property and the property type. It returns an object. Once we get the object from GetValue, we need to cast it to the proper type. So we have Name = (string)info.GetValue(. This will set our Name property to the result of GetValue after we turned it into a string. For the first parameter, we use the same string we used when adding the value, "Name". Name = (string)info.GetValue("Name", . For the second parameter, we need to tell it what kind of object it is looking for. So we pass it the typeof(string). Now we have one line to get our Name out of serialization.
Name = (string)info.GetValue("Name", typeof(string));


We just need to do the same thing for the other properties we want. The order doesn't matter. That is why there is a name, so you can add or remove them in any order.
        public Item(SerializationInfo info, StreamingContext ctxt)
        {
            Name = (string)info.GetValue("Name", typeof(string));
            Type = (ItemType)info.GetValue("Type", typeof(ItemType));
            Strength = (int)info.GetValue("Strength", typeof(int));
        }




Serialize Unit
We need to do the same thing for our Unit class. It is important that any class you want to convert to XML, including properties that are classes, are serialized. Otherwise the serializer will not be able to convert the class.

GetObjectData is the easiest to do. Simply add all of our properites. The Unit class has four properties.
        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Name", Name);
            info.AddValue("Weapon", Weapon);
            info.AddValue("Shield", Shield);
            info.AddValue("Bag", Bag);
        }


Wait, Bag is a complex property. It is a List of Items. What are we going to do? Ignore it! Serializers know about Collections. All you have to worry about is telling the Serializer that it is a collection. You don't have to here, it will figure out what object it is when you hand it over.

The next step is to write a new constructor. If you understood the last section, you should be able to build this:
        public Unit(SerializationInfo info, StreamingContext ctxt)
        {
            Name = (string)info.GetValue("Name", typeof(string));
            Weapon = (Item)info.GetValue("Weapon", typeof(Item));
            Shield = (Item)info.GetValue("Shield", typeof(Item));
        }


We're missing the information on Bag. How do we get it from info? We tell it it's a collection! typeof(List<Item>) How do we store it into our Bag property? We cast it to a collection! Add this line to the constructor:
Bag = (List<Item>)info.GetValue("Bag", typeof(List<Item>));


You just turned the two classes into serializable classes. It takes a bit of effort, but it will make everything work in the end. You can now pass those around to readers and writers and not do any complex work.



Generics and XML
I will make this XML thing easy. Create a new class. Call it MyXML. Now we'll create two static methods, one to read objects and one to save objects.

Now we'll use generics. Generics is what collections like List use. We make one method, and that method can be used for all sorts of datatypes. So that means we will create a class that saves and gets objects from XML files. This will work for the Unit class, but it will also work for any other class that has been properly serialized.

We need three using statements for this class.
using System.Xml.Serialization;
using System.Xml;
using System.IO;


We'll create a method called SaveObject. It will be public static, and return a boolean value to show whether it worked. It will take two parameters, an object to save and a filename to save it. To create a generic, we use a placeholder for the datatype. I'll use the letter 'T' (the normal convention), but you can use any letter.
        public static bool SaveObject<T>(T obj, string FileName)
        {
        }


After the method name, I inserted <T>. This tells the compiler that this is a generic and can be used with any datatype. The parameter obj is of type T. So the compiler knows that if a string is passed, than type T is string. If Unit is passed, type T is Unit. Now we can use T as if it were any other datatype.

The first step is to create an XMLSerializer. The constructor of the XMLSerializer requires a datatype. Well, we have the object to send it, so we just use the parameter obj. The GetType method is an Object scope variable, so it will work with any C# Object.
var x = new XmlSerializer(obj.GetType());


Next, we create a StreamWriter. This will write the actual XML file. We pass it the FileName we got as a parameter. We also pass it false. The second parameter is a boolean value of whether to append the file. False means we'll overwrite the file, if it exists already.
using (var Writer = new StreamWriter(FileName, false))


The only thing we need to do is call the Serialize method of x. We'll pass it the Writer and obj. then return true.
        x.Serialize(Writer, obj);
}
return true;


I wrapped it in a try/catch block. We should check to verify that the FileName is a full path that has write access. There will also be an error if the object passed isn't serializable. The full function is as follows.
        public static bool SaveObject<T>(T obj, string FileName)
        {
            try
            {
                var x = new XmlSerializer(obj.GetType());
                using (var Writer = new StreamWriter(FileName, false))
                {
                    x.Serialize(Writer, obj);
                }
                return true;
            }
            catch
            {
                return false;
            }
        }


Now we create a similar function that does the reverse. It also returns a boolean value so we know if it worked. We pass it the object to save to, and the filename. The object needs to be by reference. That way the function can edit it, and the caller knows the function will edit it. We create an instance of FileStream using (FileStream stream = new FileStream(FileName, FileMode.Open)). This uses the FileName passed, and says that the stream will be open (instead of say create).

Now create an XMLTextReader XmlTextReader reader = new XmlTextReader(stream). This will use the stream to read an XML file. Now create an XMLSerializer var x = new XmlSerializer(obj.GetType()). Again we call the GetType on Object so the XMLSerializer knows what type of object it will be. Next we call Deserialize and cast into our object type T obj = (T)x.Deserialize(reader);. The full function is as follows.

        public static bool GetObject<T>(ref T obj, string FileName)
        {
            try
            {
                using (FileStream stream = new FileStream(FileName, FileMode.Open))
                {
                    XmlTextReader reader = new XmlTextReader(stream);
                    var x = new XmlSerializer(obj.GetType());
                    obj = (T)x.Deserialize(reader);
                    return true;
                }
            }
            catch
            {
            }
            return false;
        }


You may think you want to return an object of type T and just pass it a FileName. This won't work. To call obj.GetType(), obj need to be instantiated. I don't know of a way to generically create an obj of type T. Since we need to pass it an instantiated obj, we might as well pass by reference and do the work there.



Putting It Together
So we had to add one function and one constructor for each class we want to turn into XML. We also created a new class, but made it generic so you can use it over and over again in different projects.

It is easy to use. First, we create a Unit and a FileName.
            string FileName = "MyUnit.XML";
            Item MyWeapon = new Item("Sword", ItemType.Weapon, 10);
            Item MyShield = new Item("Shield", ItemType.Shield, 8);
            Item MyPotion = new Item("Health Potion", ItemType.Potion, 5);
            Item BackUpWeapon = new Item("Ax", ItemType.Weapon, 7);
            Unit Dan = new Unit("Dan");
            Dan.Weapon = MyWeapon;
            Dan.Shield = MyShield;
            Dan.Bag.Add(MyPotion);
            Dan.Bag.Add(BackUpWeapon);


To turn it into XML, we just call the function
MyXML.SaveObject(Dan, FileName);
. That's it. You can check the XML file.

To pull it out of XML is just as easy.
Unit Dan2 = new Unit();
MyXML.GetObject(ref Dan2, FileName);


I hope this tutorial has been helpful. I know this has helped me in my coding. I included a .zip of my project for easier viewing.

Attached File(s)



Is This A Good Question/Topic? 8
  • +

Replies To: Reading and Writing XML using Serialization

#2 eclipsed4utoo  Icon User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1524
  • View blog
  • Posts: 5,958
  • Joined: 21-March 08

Posted 25 September 2010 - 07:51 AM

Great Tutorial.
Was This Post Helpful? 0
  • +
  • -

#3 DanielLeone  Icon User is offline

  • D.I.C Head

Reputation: 22
  • View blog
  • Posts: 177
  • Joined: 04-February 12

Posted 09 April 2012 - 06:23 AM

I know this post is a fair bit old, but I just wanted to say that for the last few weeks I have been trying to figure out a way to serialize and deserialize Xml.

This is probably the only simple, clear and straight forward tutorial on this topic I have managed to find. It uses a simple class, with 2 simple methods that can be used at any point in runtime, and achieve the desired job efficiently , with no over complicated code; Perfect.

These are the types of tutorials, that make me realize why I love programming, and why I have continued to persist, and why I will continue to do so.

Thanks you so much so this Robin,
Is is greatly appreciated.
Daniel,

P.S. I know this is old, but I was just wondering what the use of Streaming Context was is the GetObjectData, and Unit constructor methods were. They didn't seem to serve a purpose.

This post has been edited by DanielLeone: 09 April 2012 - 06:26 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1