Tuesday, May 24, 2011

XML Serialization

 I'm going to demonstrate how to serialize your own objects to and from an XML file.

Since .NET can use reflection to get property names, basic serialization is unbelievably simple.
It only gets slightly difficult when you want to name your XML tags differently than your property
names (but still not very hard). If you've ever used an XML serialization package in C++ like boost,
tinyXML, or libXML2, you'll see how comparatively easy C# is to use.

Let's start with a basic example. Below is an object that stores some information about a Album.

public class Album
{
  public string Title
  { get; set; }

  public int Rating
  { get; set; }

  public DateTime ReleaseDate
  { get; set; }
}

All right, now that we have an object, let's write a function that will save it to XML.

static public void SerializeToXML(Album Album)
{
  XmlSerializer serializer = new XmlSerializer(typeof(Album));
  TextWriter textWriter = new StreamWriter(@"C:\Album.xml");
  serializer.Serialize(textWriter, Album);
  textWriter.Close();
}

The first thing I do is create an XMLSerializer (located in the System.Xml.Serialization namespace)
that will serialize objects of type Album. The XMLSerializer will serialize objects to a stream, so we'll
have to create one of those next. In this case, I want to serialize it to a file, so I create a TextWriter.
I then simply call Serialize on the XMLSerializer passing in the stream (textWriter) and the object
(Album). Lastly I close the TextWriter because you should always close opened files. That's it! Let's
create a Album object and see how this is used.

static void Main(string[] args)
{
  Album Album = new Album();
  Album.Title = "Starship Troopers";
  Album.ReleaseDate = DateTime.Parse("11/7/1997");
  Album.Rating = 6.9f;

  SerializeToXML(Album);
}

static public void SerializeToXML(Album Album)
{
  XmlSerializer serializer = new XmlSerializer(typeof(Album));
  TextWriter textWriter = new StreamWriter(@"C:\Album.xml");
  serializer.Serialize(textWriter, Album);
  textWriter.Close();
}

After this code executes, we'll have an XML file with the contents of our Album object.

<?xml version="1.0" encoding="utf-8"?>
<Album xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Title>Starship Troopers</Title>
  <Rating>6.9</Rating>
  <ReleaseDate>1997-11-07T00:00:00</ReleaseDate>
</Album>

If you noticed, all of the XML tag names are the same as the property names. If we want to
change those, we can simply add an attribute above each property that sets the tag name.

public class Album
{
  [XmlElement("AlbumName")]
  public string Title
  { get; set; }

  [XmlElement("AlbumRating")]
  public float Rating
  { get; set; }

  [XmlElement("AlbumReleaseDate")]
  public DateTime ReleaseDate
  { get; set; }
}

Now when the same code is executed again, we get our custom tag names.

<?xml version="1.0" encoding="utf-8"?>
<Album xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <AlbumName>Starship Troopers</AlbumName>
  <AlbumRating>6.9</AlbumRating>
  <AlbumReleaseDate>1997-11-07T00:00:00</AlbumReleaseDate>
</Album>

Sometimes, in XML, you want information stored as an attribute of another tag instead of a tag
by itself. This can be easily accomplished with another property attribute.

public class Album
{
  [XmlAttribute("AlbumName")]
  public string Title
  { get; set; }

  [XmlElement("AlbumRating")]
  public float Rating
  { get; set; }

  [XmlElement("AlbumReleaseDate")]
  public DateTime ReleaseDate
  { get; set; }
}

With this code, AlbumName will now be an attribute on the Album tag.

<?xml version="1.0" encoding="utf-8"?>
<Album xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   AlbumName="Starship Troopers">
  <AlbumRating>6.9</AlbumRating>
  <AlbumReleaseDate>1997-11-07T00:00:00</AlbumReleaseDate>
</Album>

Let's move on to something a little more interesting. Let's create another Album and serialize a
List of them to our XML file. Here's the modified code to do just that:

static void Main(string[] args)
{
  Album Album = new Album();
  Album.Title = "Starship Troopers";
  Album.ReleaseDate = DateTime.Parse("11/7/1997");
  Album.Rating = 6.9f;

  Album Album2 = new Album();
  Album2.Title = "Ace Ventura: When Nature Calls";
  Album2.ReleaseDate = DateTime.Parse("11/10/1995");
  Album2.Rating = 5.4f;

  List<Album> Albums = new List<Album>() { Album, Album2 };

  SerializeToXML(Albums);
}

static public void SerializeToXML(List<Album> Albums)
{
  XmlSerializer serializer = new XmlSerializer(typeof(List<Album>));
  TextWriter textWriter = new StreamWriter(@"C:\Album.xml");
  serializer.Serialize(textWriter, Albums);
  textWriter.Close();
}

Now we have XML that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfAlbum xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Album AlbumName="Starship Troopers">
    <AlbumRating>6.9</AlbumRating>
    <AlbumReleaseDate>1997-11-07T00:00:00</AlbumReleaseDate>
  </Album>
  <Album AlbumName="Ace Ventura: When Nature Calls">
    <AlbumRating>5.4</AlbumRating>
    <AlbumReleaseDate>1995-11-10T00:00:00</AlbumReleaseDate>
  </Album>
</ArrayOfAlbum>

Ok, so you can see how easy it is to get your objects into an XML document. Let's now look at
how to read an XML document back into our objects - deserialization. The process of deserializing
is very similar to what we did for serialization.

static List<Album> DeserializeFromXML()
{
   XmlSerializer deserializer = new XmlSerializer(typeof(List<Album>));
   TextReader textReader = new StreamReader(@"C:\Album.xml");
   List<Album> Albums;
   Albums = (List<Album>)deserializer.Deserialize(textReader);
   textReader.Close();

   return Albums;
}

Just like before, we first create an XmlSerializer that can deserialize objects of type List<Album>.
The XmlSerializer also deserializes from a stream, so we create a file stream from our XML file.
We then simply call
Deserialize on the stream and cast the output to our desired type. Now the
Albums List is populated with objects that we previously serialized to the XML file.

The deserializer is very good at handling missing pieces of information in your XML file. Let's say
the second Album didn't have the AlbumName attribute on the Album tag. When the XML file is
deserialized, it simply populates that field with null. If AlbumRating wasn't there, you'd receive 0.
Since a DateTime object can't be null, if AlbumReleaseDate was missing, you'd receive
DateTime.MinValue (1/1/0001 12:00:00AM).

If the XML document contains invalid syntax, like say the first opening Album tag was missing,
the Deserialize call will fail with an InvalidOperationException. It will also be kind enough to
specify the location in the file where it encountered the error (line number, column number).

One thing to remember is that the basic XML serialization won't maintain references. Let's say
I populated my Albums list with the same Album reference multiple times:

Album Album = new Album();
Album.Title = "Starship Troopers";
Album.ReleaseDate = DateTime.Parse("11/7/1997");
Album.Rating = 6.9f;

List<Album> Albums = new List<Album>() { Album, Album };

Now I have a list containing two of the exact same Album reference. When I serialize and
deserialize this list, it will be converted to two separate instances of the Album object - they
would just have the same information. Along this same line, the XMLSerializer also doesn't
support circular references. If you need this kind of flexibility, you should consider binary serialization.

There's still a lot to cover when it comes to XML serialization, but I think this tutorial covers enough of
the basics to get things rolling. If you've got questions or anything else to say, leave us a comment.

 

1 comment :

  1. xml is a very interesting language to be used and contain the data object model or abbreviated DOM.tutorial very good and hopefully can help me in building a web application thanks

    ReplyDelete