Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

XML Serialization on a Collection of Multiple Types that are Unknown

5.00/5 (15 votes)
22 Jan 2009CPOL2 min read 90K  
Shows how to do XML serialization on a collection of multiple types when the types are not known.

Introduction

Have you ever wanted to serialize a configuration object that has a collection that contains an interface or abstract class? Do you not know all the types that you are going to possibly serialize? Well, if that's the case, then I may be able to help you out.

Using the code

OK.. Let's get started. You'll first want to take the following helper class and paste it into your project. It will help you with serializing objects into XML in one line of code.

C#
/// <summary>
/// A generic class used to serialize objects.
/// </summary>
public class GenericSerializer
{
    /// <summary>
    /// Serializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be serialized.</typeparam>
    /// <param name="obj">The object to be serialized.</param>
    /// <returns>String representation of the serialized object.</returns>
    public static string Serialize<T>(T obj)
    {
        XmlSerializer xs = null;
        StringWriter sw = null;
        try
        {
            xs = new XmlSerializer(typeof(T));
            sw = new StringWriter();
            xs.Serialize(sw, obj);
            sw.Flush();
            return sw.ToString();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (sw != null)
            {
                sw.Close();
                sw.Dispose();
            }
        }
    }
    public static string Serialize<T>(T obj, Type[] extraTypes)
    {
        XmlSerializer xs = null;
        StringWriter sw = null;
        try
        {
            xs = new XmlSerializer(typeof(T), extraTypes);
            sw = new StringWriter();
            xs.Serialize(sw, obj);
            sw.Flush();
            return sw.ToString();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (sw != null)
            {
                sw.Close();
                sw.Dispose();
            }
        }
    }
    /// <summary>
    /// Serializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be serialized.</typeparam>
    /// <param name="obj">The object to be serialized.</param>
    /// <param name="writer">The writer to be used for output in the serialization.</param>
    public static void Serialize<T>(T obj, XmlWriter writer)
    {
        XmlSerializer xs = new XmlSerializer(typeof(T));
        xs.Serialize(writer, obj);
    }
    /// <summary>
    /// Serializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be serialized.</typeparam>
    /// <param name="obj">The object to be serialized.</param>
    /// <param name="writer">The writer to be used for output in the serialization.</param>
    /// <param name="extraTypes"><c>Type</c> array
    ///       of additional object types to serialize.</param>
    public static void Serialize<T>(T obj, XmlWriter writer, Type[] extraTypes)
    {
        XmlSerializer xs = new XmlSerializer(typeof(T), extraTypes);
        xs.Serialize(writer, obj);
    }
    /// <summary>
    /// Deserializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be deserialized.</typeparam>
    /// <param name="reader">The reader used to retrieve the serialized object.</param>
    /// <returns>The deserialized object of type T.</returns>
    public static T Deserialize<T>(XmlReader reader)
    {
        XmlSerializer xs = new XmlSerializer(typeof(T));
        return (T)xs.Deserialize(reader);
    }
    /// <summary>
    /// Deserializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be deserialized.</typeparam>
    /// <param name="reader">The reader used to retrieve the serialized object.</param>
    /// <param name="extraTypes"><c>Type</c> array
    ///           of additional object types to deserialize.</param>
    /// <returns>The deserialized object of type T.</returns>
    public static T Deserialize<T>(XmlReader reader, Type[] extraTypes)
       
    {
        XmlSerializer xs = new XmlSerializer(typeof(T), extraTypes);
        return (T)xs.Deserialize(reader);
    }
    /// <summary>
    /// Deserializes the given object.
    /// </summary>
    /// <typeparam name="T">The type of the object to be deserialized.</typeparam>
    /// <param name="XML">The XML file containing the serialized object.</param>
    /// <returns>The deserialized object of type T.</returns>
    public static T Deserialize<T>(string XML)
    {
        if (XML == null || XML == string.Empty)
            return default(T);
        XmlSerializer xs = null;
        StringReader sr = null;
        try
        {
            xs = new XmlSerializer(typeof(T));
            sr = new StringReader(XML);
            return (T)xs.Deserialize(sr);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (sr != null)
            {
                sr.Close();
                sr.Dispose();
            }
        }
    }
    public static T Deserialize<T>(string XML, Type[] extraTypes)
    {
        if (XML == null || XML == string.Empty)
            return default(T);
        XmlSerializer xs = null;
        StringReader sr = null;
        try
        {
            xs = new XmlSerializer(typeof(T), extraTypes);
            sr = new StringReader(XML);
            return (T)xs.Deserialize(sr);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (sr != null)
            {
                sr.Close();
                sr.Dispose();
            }
        }
    }
    public static void SaveAs<T>(T Obj, string FileName, 
                       Encoding encoding, Type[] extraTypes)
    {
        if (File.Exists(FileName))
            File.Delete(FileName);
        DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(FileName));
        if (!di.Exists)
            di.Create();
        XmlDocument document = new XmlDocument();
        XmlWriterSettings wSettings = new XmlWriterSettings();
        wSettings.Indent = true;
        wSettings.Encoding = encoding;
        wSettings.CloseOutput = true;
        wSettings.CheckCharacters = false;
        using (XmlWriter writer = XmlWriter.Create(FileName, wSettings))
        {
            if (extraTypes != null)
                Serialize<T>(Obj, writer, extraTypes);
            else
                Serialize<T>(Obj, writer);
            writer.Flush();
            document.Save(writer);
        }
    }
    public static void SaveAs<T>(T Obj, string FileName, Type[] extraTypes)
    {
        SaveAs<T>(Obj, FileName, Encoding.UTF8, extraTypes);
    }
    public static void SaveAs<T>(T Obj, string FileName, Encoding encoding)
    {
        SaveAs<T>(Obj, FileName, encoding, null);
    }
    public static void SaveAs<T>(T Obj, string FileName)
    {
        SaveAs<T>(Obj, FileName, Encoding.UTF8);
    }
    public static T Open<T>(string FileName, Type[] extraTypes)
    {
        T obj = default(T);
        if (File.Exists(FileName))
        {
            XmlReaderSettings rSettings = new XmlReaderSettings();
            rSettings.CloseInput = true;
            rSettings.CheckCharacters = false;
            using (XmlReader reader = XmlReader.Create(FileName, rSettings))
            {
                reader.ReadOuterXml();
                if (extraTypes != null)
                    obj = Deserialize<T>(reader, extraTypes);
                else
                    obj = Deserialize<T>(reader);
            }
        }
        return obj;
    }
    public static T Open<T>(string FileName)
    {
        return Open<T>(FileName, null);
    }
}

OK.. Now that we have that out of the way, let's take a look at our real problem. Take for instance, we have an abstract class, Animal. Then, we have two classes that inherit this class, Human and Dog.

C#
public abstract class Animal
{
    public abstract int Legs
    {
        get;
        set;
    }

    public abstract bool HasTail
    {
        get;
        set;
    }
}

public class Human : Animal
{

    public Human()
    {
        this.Legs = 2;
        this.HasTail = false;
    }

    public override int Legs
    {
        get;
        set;
    }

    public override bool HasTail
    {
        get;
        set;
    }

}

public class Dog : Animal
{

    public Dog()
    {
        this.Legs = 4;
        this.HasTail = true;
        this.Breed = "Black Lab";
    }

    public override int Legs
    {
        get;
        set;
    }

    public override bool HasTail
    {
        get;
        set;
    }

    public string Breed
    {
        get;
        set;
    }
}

OK... Now, we have an object that we want to be able to serialize. It's called HouseHold, and it has a collection of Animals as one of its properties.

C#
public class HouseHold
{
    public HouseHold()
    {
        this.Residents = new List<Animal>();
    }

    public List<Animal> Residents
    {
        get;
        set;
    }
}

Now, we want to serialize this object.

C#
try
{
    HouseHold hh = new HouseHold();
    hh.Residents.Add(new Human());
    hh.Residents.Add(new Human());
    hh.Residents.Add(new Dog());

    string xml = GenericSerializer.Serialize<HouseHold>(hh);
    Console.WriteLine(xml);
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}

Console.Read();

You'll notice that we get the following exception:

"There was an error generating the XML document."

with an inner exception of:

"The type ClassLibrary1.Human was not expected. 
Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

The keyword there is "statically". Yes, we could go and statically place the XmlInclude attribute on top of the class, but what if we don't know all the classes that inherit from Animal? What if our application is pluggable and needs to be able to use types that are generated by customers? Well, let's move forward, and I'll show you how this can be done.

The first thing we'll need to do is have our HouseHold class implement IXmlSerializable so that we can intercept the serialization of this object and serialize it ourselves. Next, we'll need to create a method that retrieves all of our types. In this example, we'll query the current assembly using Reflection for any class that inherits Animal.

C#
public class HouseHold : IXmlSerializable
{
    private static Type[] _animalTypes;

    static HouseHold()
    {
        _animalTypes = GetAnimalTypes().ToArray();
    }

    public HouseHold()
    {
        this.Residents = new List<Animal>();
    }

    public List<Animal> Residents
    {
        get;
        set;
    }

    #region IXmlSerializable Members

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();
        if (wasEmpty)
            return;

        reader.MoveToContent();
        reader.ReadStartElement("Residents");
        this.Residents = GenericSerializer.Deserialize<List<Animal>>(reader, _animalTypes);
        reader.ReadEndElement();

        //Read Closing Element
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteStartElement("Residents");
        GenericSerializer.Serialize<List<Animal>>(this.Residents, writer, _animalTypes);
        writer.WriteEndElement();
    }

    #endregion

    public static List<Type> GetAnimalTypes()
    {

        List<Type> types = new List<Type>();

        Assembly asm = typeof(HouseHold).Assembly;

        Type tAnimal = typeof(Animal);

        //Query our types. We could also load any other assemblies and
        //query them for any types that inherit from Animal

        foreach (Type currType in asm.GetTypes())
        {
        if (!currType.IsAbstract
            && !currType.IsInterface
            && tAnimal.IsAssignableFrom(currType))
            types.Add(currType);
        }

        return types;
    }
}

OK... Now, we will run our test code, and everything should run through just fine. I hope this helped you out a lot. Let me know what you think.

License

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