Tutorial: Xml Serialization, Part 2

Posted by Matt | Filed under , , ,

Project: XmlSerialization.zip (11.03 kb)

In part 1, we introduced Xml Serialization.  In that post, we created a Family class, added some family members to it and serialized it to an Xml file.

But what if we need to specialize the family members?  What if we want a special Father class and Mother classes?  This can be done too:

[Serializable]
public class Mother : FamilyMember
{
  public override short Age
  {
    get { return 29; }
    set { /* never ages */ }
  }
}

[Serializable]
public class Father : FamilyMember
{
  public short GolfHandicap { get; set; }
}

If we construct our family:

Family f = new Family();
f.Name = "Smith";
f.Members.Add(new Father() { Name = "John", 
    Age = 50, GolfHandicap = 12 });
f.Members.Add(new Mother() { Name = "Mary", 
    Age = 50 });

and write, we get:

image_thumb2

We get an error.  Xml serialization does not like this.  This is because when it is serializing the Family object, it does not know about the Father and Mother classes.  The Members member is just a list of FamilyMembers.

There are actually 2 fixes for this, each produces different results.

The first solution is to add the [XmlInclude] attribute before the FamilyMember class, once for each sub-class.  This notifies the Xml serialization engine about the sub-classes.

[Serializable]
[XmlInclude(typeof(Father)), 
  XmlInclude(typeof(Mother))]
public class FamilyMember
{
...
	

Now, if we try writing again, we'll get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <FamilyMember xsi:type="Father">
      <Name>John</Name>
      <Age>50</Age>
      <GolfHandicap>12</GolfHandicap>
    </FamilyMember>
    <FamilyMember xsi:type="Mother">
      <Name>Mary</Name>
      <Age>29</Age>
    </FamilyMember>
  </Members>
</Family>

and this will correctly read as well.

The second solution is to instead use the [XmlArrayElement] attribute on the Members member of the Family class.

[XmlArrayItem(typeof(Father)),
  XmlArrayItem(typeof(Mother))]
public List<FamilyMember> Members
{
...

If we write using this, we'll get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <Father>
      <Name>John</Name>
      <Age>50</Age>
      <GolfHandicap>12</GolfHandicap>
    </Father>
    <Mother>
      <Name>Mary</Name>
      <Age>29</Age>
    </Mother>
  </Members>
</Family>

If you have existing Xml files to be read, the second solution will more likely match your data.

So there you have it.

There's many more details and attributes that you can get into for working with Xml files, but this will give you a start for many common cases you may require.

Xml serialization in C# is very easy.  If you've read the code above, you'll notice that there isn't a single bit of code dealing with actual parsing of Xml.  All that is handled automatically by magic the C# engine.  Reading an existing Xml file is as simple as creating a set of classes for the different elements in the data and using attributes to tweak the reading a bit.

I've included the project above.  It's based on C# 3.5 using Visual Studio 2008, but can easily be modified for C# 2.0.

Tutorial: Xml Serialization, Part 1

Posted by Matt | Filed under , , ,

The best feature about C# has got to be Xml Serialization.

Creating an Xml reader/writer in C++ using MSXML or LibXml takes significant time and debugging.  Creating the same Xml reader/writer in C# using classes is very easy.  Admittedly, if I am using C++, I have no problems using LibXml, but if given a choice, I'd use C# first.

To make a class Xml Serializable, you need to:

  1. make the class public, and
  2. give it the Serializable attribute.

The follow class can be read/written to Xml:

[Serializable]
public class Family
{
}

This class won't do anything, but you can use the follow code to write it to file:

Family f = new Family();
// TODO: Fill the family object

XmlSerializer xmlSerializer = new XmlSerializer(typeof(Family));
TextWriter writer = new StreamWriter("Family.xml");
xmlSerializer.Serialize(writer, f);
writer.Close();

and the following code to read from file:

XmlSerializer xs = new XmlSerializer(typeof(Family));
TextReader reader = new StreamReader("Family.xml");
Family f = (Family)xs.Deserialize(reader);

The resulting Xml file will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

Pretty basic and not much fun.

Every family I know has a family name, so let's add a name to the family:

[Serializable]
public class Family
{
  public string Name { get; set; }
}

(Please note that I am using C# 3.5 short-notation for members.)

If we set the family name and serialize it, we'll get the following:

<?xml version="1.0" encoding="utf-8"?>
<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Smith</Name>
</Family>

Notice that the Name member of Family became a child element of Family in the Xml tree.  This is the default behaviour of all public members.  By default, C# will serialize all public members of a class as sub-elements.

In my case, I'd prefer Name to be an attribute of family instead of a sub-element.  To do this, I use the [XmlAttribute] attribute on the Name member:

[Serializable]
public class Family
{
  [XmlAttribute]
  public string Name { get; set; }
}

to get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith" />

This is now more to my liking.

Let's add some family members to this family.  First, define a family member class:

[Serializable]
public class FamilyMember
{
  public string Name { get; set; }
  public short Age { get; set; }
}

and add a list of family members to our family class:

private List<FamilyMember> _members = new List<FamilyMember>();
public List<FamilyMember> Members
{
  get { return _members; }
  set { _members = value; }
}

If we add some members to our family, and serialize, we get the following:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <FamilyMember>
      <Name>John</Name>
      <Age>51</Age>
    </FamilyMember>
    <FamilyMember>
      <Name>Mary</Name>
      <Age>50</Age>
    </FamilyMember>
  </Members>
</Family>

You'll notice that we have 2 family members each with their own data.

In part 2, we will specialize the family members into derived classes of FamilyMember.