Click here to Skip to main content
15,879,348 members
Articles / Mobile Apps

XML Serialize Hashtable, Collections and Generics using IXmlSerializable interface

Rate me:
Please Sign up or sign in to vote.
4.82/5 (10 votes)
18 Jun 2008CPOL4 min read 77.9K   831   35   1
This article shows how to use XmlSerializer to serialize types that usually cannot be XML Serialized

Introduction

This article discusses how to serialize classes that usually cannot be serialized with XmlSerializer, by implementing IXmlSerializer interface. This article will show 3 different implementations.

Background

I usually have to serialize object into XML files, to save the config and what you can imagine. I was tired of XmlSerialization limits, so was everybody, and I searched CodeProject to find out how I can do it. There are a lot of articles about those problems. You can serialize Hashtable using binary, but this is not XML. You can serialize all that you want by manually doing the serialization element by element, but if you have to do this for big or complex classes, it could be a lot of work.

Finally, I discovered the IXmlSerializable interface. It allows to modify the behavior of the XmlSerializer. So far, I found nothing about this interface on CodeProject, which is my reference. So, I decided to write this article. In this article, I will talk only about the XmlSerializer, and how IXmlSerializable interface can be used through it.

IXmlSerializer

When you serialize an object using XmlSerializer, there are two cases, first (general case) the object does not implement the IXmlSerializable interface, and the XmlSerializer uses Reflection to write or read XML files. In the second case, your object implements IXmlSerializable and instead of Reflection, the IXmlSerializable.WriteXml and IXmlSerializable.ReeadXml methods are called:

C#
public interface IXmlSerializable
{
    XmlSchema GetSchema();
    void ReadXml(XmlReader reader);
    void WriteXml(XmlWriter writer);
}       

First Implementation Example: HastableSerializable

This example is from Matt Berther. It is the best and simplest example that I have found, so thank you Matt. Here is the code class, it is really simple.

C#
public class HashtableSerializable : Hashtable, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema(){ ... }
public void ReadXml(System.Xml.XmlReader reader){ ... }
public void WriteXml(System.Xml.XmlWriter writer){ ... }
}

I do not need to get a schema, this article is not about this implementation. Look at the WriteXml method:

C#
public void WriteXml(System.Xml.XmlWriter writer)
{
    // Write the root element 
    writer.WriteStartElement("dictionary");

    // Foreach object in this (as i am a Hashtable)
    foreach(object key in this.Keys)
    {
        object value = this[key];
        // Write item, key and value
        writer.WriteStartElement("item");
        writer.WriteElementString("key", key.ToString()); 
        writer.WriteElementString("value", value.ToString());
        // write </item>
        writer.WriteEndElement();
     }
 
     // write </dictionary>
     writer.WriteEndElement();
     }
}

We just write XML like this one:

XML
<dictionary>
    <item>
       <key>...</key>
       <value>...</value>
    </item>
</dictionary>

Now we have to re-read it. Please now take a look at the ReadXml method. When the calling XmlSerialize calls it, the reader parameter has its position just at the right place for us. Be careful to place it at the right place before returning so the XmlSerializer can continue the deserialization.

C#
public void ReadXml(System.Xml.XmlReader reader)
{
    // Start to use the reader.
    reader.Read();
    // Read the first element i.e. root of this object
    reader.ReadStartElement("dictionary");

    // Read all elements
    while(reader.NodeType != XmlNodeType.EndElement)
    {
        // parsing the item
        reader.ReadStartElement("item");

        // Parsing the key and value 
        string key = reader.ReadElementString("key");
        string value = reader.ReadElementString("value");

        // end reading the item.
        reader.ReadEndElement();
        reader.MoveToContent();

        // add the item
        this.Add(key, value);
     }

     // Extremely important to read the node to its end.
     // next call of the reader methods will crash if not called.
     reader.ReadEndElement();
}

Please pay particular attention to the last reader.ReadEndElement(), which consumes the last </dictionary>, and place the reader position at the end.

That's it. Now if I need to serialize a Hashtable, member of my Class1, I just have to use a HashtableSerializable instead.

Second Implementation Example: Override the XmlSerialiser, When It's Already Working

Historically, I have a ByteArray class that allows me to manipulate bytes. This is an old class from Framework 1.1 and it implements CollectionBase (It compiles in framework 2). This class implements Icollection, IEnumerable, and can be implicitly converted from and into byte[]. So it is serializable using XmlSerializable, with this result:

XML
<ArrayOfUnsignedByte 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <unsignedByte>1</unsignedByte>
    <unsignedByte>2</unsignedByte>
    <unsignedByte>3</unsignedByte>
    <unsignedByte>4</unsignedByte>
    <unsignedByte>5</unsignedByte>
    <unsignedByte>6</unsignedByte>
    <unsignedByte>7</unsignedByte>
    <unsignedByte>8</unsignedByte>
    <unsignedByte>9</unsignedByte>
    <unsignedByte>10</unsignedByte>
    <unsignedByte>11</unsignedByte>
    <unsignedByte>12</unsignedByte>
    <unsignedByte>13</unsignedByte>
    <unsignedByte>14</unsignedByte>
    <unsignedByte>15</unsignedByte>
</ArrayOfUnsignedByte>

Good, but I want to get a more readable result, and written in hexadecimal. So guess what? I implement IXmlSerializable!

(ByteArray.Parse(string hexa) parses a string like 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F, and converts it in a ByteArray, and ByteArray.ToHexString() serializes it into the string.)

C#
public void ReadXml(System.Xml.XmlReader reader)
{
    reader.Read();
    string temp = reader.ReadString();
    try
    {
       this.Add(ByteArray.Parse(temp));
    }
    catch{}
    reader.ReadEndElement();
}

public void WriteXml(System.Xml.XmlWriter writer)
{
    writer.WriteString(this.ToHexString());
}

Now I serialize that way :

XML
<?xml version="1.0" encoding="utf-8"?>
<ByteArray>01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F</ByteArray>

Third Implementation Example: Generics

First, usually I use a lot of Lists<> in my classes, and it serializes very well. Of course, it does not serialize if the objects from the list are not serializable. For this example, I have a Set<T> class created using existing CodeProject Set articles:

  1. Yet Another C# Set Class by Theo Bebekis
  2. A Set Class by PIEBALDconsult
  3. A C# Set Class based on enums by ‘RalfW’
  4. Pascal Set by Scott Mitchell
  5. Add Support for ‘Set’ Collections to .NET by Jason Smith
  6. Set Collections for C by Keith Barrett

My class is a mix of those ones. It is constructed around a Hashtable, and was not serializable, and it was a real problem for me. It is this class which leads me to IXmlSerializable, and to this article finally. I will not discuss about the Set class itself, please see previous articles, I do not create anything with that. Please note that it is a generic class. I just add IXmlSerializable implementation:

C#
public class Set<T> : ICollection<T>, IEnumerable, IXmlSerializable
                , IListSource
{
/// <summary>
/// Read an XML element corresponding to a Set of T
/// </summary>
/// <param name="reader"></param>
void IXmlSerializable.ReadXml(XmlReader reader)
{
    // this line supposes that T is XmlSerializable 
    // (not an IDictionnary for example, or implements IXmlSerializable too ...)
    System.Xml.Serialization.XmlSerializer S = 
            new System.Xml.Serialization.XmlSerializer(typeof(T));

    // Very important
    reader.Read();
  
    while(reader.NodeType != XmlNodeType.EndElement)
    {
        try
        {
            // YES it uses the XmlSerializer to serialize each item!
            // It is so simple.
            T item = (T)S.Deserialize(reader);
            if(item != null) // May be I have to throw here ?
                this.Add(item);
         }
         catch
         {
             // May be  i have to throw ??
         }
    }

    // Very important, if reader.Read()
    reader.ReadEndElement();
}

/// <summary>
/// Write an XML Element corresponding to a Set of T
/// </summary>
/// <param name="writer"></param>
void IXmlSerializable.WriteXml(XmlWriter writer)
{
    // this line supposes that T is XML Serializable 
    // (not an IDictionnary for example, or implements IXmlSerializable too ...)
    System.Xml.Serialization.XmlSerializer S = 
          new System.Xml.Serialization.XmlSerializer(typeof(T));

    foreach(T item in this.InternalHashtable.Values)
    {
        try
        {
            // YES it uses the XmlSerializer to serialize each item !
            // It is so simple.
            S.Serialize(writer, item, null);
        }
        catch//(Exception ex)
        {
             // May be I have to throw ??
             // System.Windows.Forms.MessageBox.Show(ex.ToString());
             // writer.WriteElementString(this._TypeName, null);
        }
    }
}
}

As you can see, the implementation is strange, because it uses also an XmlSerializer (don't forget that it is an XmlSerializer that calls the two methods to read and write XML.)

Points of Interest

Something seems to be missing in my ByteArray class, I didn't succeed in indenting my data correctly when I have a lot of bytes. I always get only a line using this method. I succeeded in doing many lines, but the indentation is always bad. Please show me how to do it ...

History

  • 18th June, 2008: First release 
    There might be newer versions in the future ...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer MAINtag
France France
Software manager (1 year)
Software developper, C# and device developpement. (4 years)
My homepage, My CV

Comments and Discussions

 
GeneralThanks a lot Pin
Baba Deep27-May-09 15:07
Baba Deep27-May-09 15:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.