Click here to Skip to main content
Click here to Skip to main content

XML Serialization on a Collection of Multiple Types that are Unknown

, 22 Jan 2009
Rate this:
Please Sign up or sign in to vote.
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.

/// <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.

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.

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

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

Now, we want to serialize this object.

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.

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)

About the Author

Sike Mullivan
Software Developer (Senior)
United States United States
I am a Sr SharePoint Developer in Saint Louis, MO. I've been developing professionally for about four years now. For three years I worked for a Document Imaging company that developed applications for Scanning, Indexing, Migrating, and Searching SharePoint (MOSS, WSS). I'm now working on a team for a Cable company that customizes their internal and external SharePoint implementations.

Comments and Discussions

 
QuestionXml Serialization of List(int) and int[] with extra types PinmemberMember 844063628-Nov-11 6:51 
GeneralThis problem is solvable very quickly PinmemberDaniel Gidman22-Sep-10 7:24 
Serialization for Rapid Application Development: A Better Approach
 
Just wanted to point you to a solution thats been around for a couple years. I use it in application development a lot.
GeneralRe: This problem is solvable very quickly PinmemberSike Mullivan22-Sep-10 7:39 
GeneralRe: This problem is solvable very quickly PinmemberDaniel Gidman22-Sep-10 9:21 
GeneralRe: This problem is solvable very quickly PinmemberSike Mullivan22-Sep-10 9:47 
QuestionReturns and Object Indents? PinmemberNicholas Pappas30-Jul-10 6:36 
AnswerRe: Returns and Object Indents? PinmemberSike Mullivan9-Sep-10 5:32 
QuestionWhat about if I have additional non collection type properties in HouseHold? Pinmembermgrounds3-Nov-09 7:24 
AnswerRe: What about if I have additional non collection type properties in HouseHold? PinmemberSike Mullivan3-Nov-09 7:39 
GeneralOh thank god... Pinmembervodzurk26-Mar-09 1:40 
GeneralRe: Oh thank god... PinmemberSike Mullivan26-Mar-09 5:18 
GeneralMore about extra types management Pinmembervertex8522-Jan-09 21:34 
GeneralRe: More about extra types management PinmemberSkully102223-Jan-09 3:02 
QuestionWhy not XmlIncludeAttribute? PinmemberEvgeniy Podolyak22-Jan-09 6:45 
AnswerRe: Why not XmlIncludeAttribute? PinmemberSaleem Javid22-Jan-09 8:41 
GeneralRe: Why not XmlIncludeAttribute? PinmemberEvgeniy Podolyak22-Jan-09 8:48 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 22 Jan 2009
Article Copyright 2009 by Sike Mullivan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid