Click here to Skip to main content
15,861,168 members
Articles / Programming Languages / XML

XML Serialization on a Collection of Multiple Types that are Unknown

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
22 Jan 2009CPOL2 min read 87K   62   16
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)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionXml Serialization of List(int) and int[] with extra types Pin
Member 844063628-Nov-11 6:51
Member 844063628-Nov-11 6:51 
GeneralThis problem is solvable very quickly Pin
Daniel Gidman22-Sep-10 7:24
professionalDaniel Gidman22-Sep-10 7:24 
GeneralRe: This problem is solvable very quickly Pin
Sike Mullivan22-Sep-10 7:39
Sike Mullivan22-Sep-10 7:39 
GeneralRe: This problem is solvable very quickly Pin
Daniel Gidman22-Sep-10 9:21
professionalDaniel Gidman22-Sep-10 9:21 
GeneralRe: This problem is solvable very quickly Pin
Sike Mullivan22-Sep-10 9:47
Sike Mullivan22-Sep-10 9:47 
QuestionReturns and Object Indents? Pin
Nicholas Pappas30-Jul-10 6:36
Nicholas Pappas30-Jul-10 6:36 
AnswerRe: Returns and Object Indents? Pin
Sike Mullivan9-Sep-10 5:32
Sike Mullivan9-Sep-10 5:32 
QuestionWhat about if I have additional non collection type properties in HouseHold? Pin
mgrounds3-Nov-09 7:24
mgrounds3-Nov-09 7:24 
AnswerRe: What about if I have additional non collection type properties in HouseHold? Pin
Sike Mullivan3-Nov-09 7:39
Sike Mullivan3-Nov-09 7:39 
GeneralOh thank god... Pin
vodzurk26-Mar-09 1:40
vodzurk26-Mar-09 1:40 
GeneralRe: Oh thank god... Pin
Sike Mullivan26-Mar-09 5:18
Sike Mullivan26-Mar-09 5:18 
GeneralMore about extra types management Pin
vertex8522-Jan-09 21:34
vertex8522-Jan-09 21:34 
GeneralRe: More about extra types management Pin
Sike Mullivan23-Jan-09 3:02
Sike Mullivan23-Jan-09 3:02 
QuestionWhy not XmlIncludeAttribute? Pin
Evgeniy Podolyak22-Jan-09 6:45
Evgeniy Podolyak22-Jan-09 6:45 
AnswerRe: Why not XmlIncludeAttribute? Pin
Saleem Javid22-Jan-09 8:41
Saleem Javid22-Jan-09 8:41 
GeneralRe: Why not XmlIncludeAttribute? Pin
Evgeniy Podolyak22-Jan-09 8:48
Evgeniy Podolyak22-Jan-09 8:48 

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.