Serializing ArrayLists of Disparate Objects to XML Strings
In an earlier post here, I wrote two functions to Serialize and Deserialize objects to XML strings.
Since then, while reading a book about XML, I learned of even more power in the .Net XML Serialization libraries*. In addition to serializing objects and collections of objects you can serialize ArrayLists which can contain disparate or non-homogeneous objects. Techno-babble translation: Disparate and non-homogenous mean 'different'.
*BTW: Never stop learning or you'll end up as geek road-kill on the nerd highway.
I wrote two functions (in XMLUtils namespace) to encapsulate the logic. What do these functions do? Why I'd be delighted to show you. Here's an example of the functions in action:
ArrayList MyArrayList = new ArrayList();MyArrayList.Add(new MyNameSpace.MyClass("Sue"));MyArrayList.Add(DateTime.Now);
MyArrayList.Add(new MyClass("Bob"));MyArrayList.Add(1234);Response.Write("----- Before serialization ------ <br />");
foreach (Object O in MyArrayList)Response.Write(O.ToString() + "<br />");
String MyXml = XMLUtils.SerializeArrayList(MyArrayList); // SerializeMyArrayList = XMLUtils.DeSerializeArrayList(MyXml); // DeserializeResponse.Write("----- After deserialization ------ <br />");
foreach (Object O in MyArrayList)Response.Write(O.ToString() + "<br />");
// Output:
// ----- Before serialization ------
// Sue
// 1/5/2010 12:50:15 PM
// Bob
// 1234
// ----- After deserialization ------
// Sue
// 1/5/2010 12:50:15 PM
// Bob
// 1234
The example shows an ArrayList being created with a custom class, a DateTime and an integer…disparate, non-homogeneous objects.
The ArrayList is then serialized into an XML string.
The XML string is then deserialized into a new ArrayList with all the objects intact.
This could be useful…yah?
To get this to work, one of the overloaded Serialize functions in the XmlSerializer class is used. The Serialize function is passed an array of types to be serialized. It is easy to get all the types in the ArrayList by iterating the list.
The problem is deserializing the list. How do you get the array of types the deserializer needs?
My Solution: Add the list of types in the ArrayList…to the ArrayList…before serializing it.
The solution has one drawback: Deserialization happens twice. The XmlString is deserialized to get the array of types. Once the array of types is collected, the XmlString is deserialized again with the array of types and the ArrayList will be reconstituted correctly. The XmlString could be parsed to get the array of types but it would probably be just as slow as deserializing and definitely more complicated. The list of types could be included as auxiliary information in a custom class but my self-imposed requirement was to serialize to a valid XML document which requires a single root element…so the list of types must be within the root element.
Here are the functions:
/// ---- SerializeArrayList ----------------
/// <summary>
/// Serialize an ArrayList to an XML string
/// </summary>
/// <param name="ArrayListIn">An ArrayList</param>
/// <returns>XML String</returns>
public static string SerializeArrayList(ArrayList ArrayListIn)
{
// get a list of all the types in the ArrayList to serialize
// avoid duplicates
ArrayList TypesInList = new ArrayList();
foreach (Object Item in ArrayListIn)
{
if (TypesInList.Contains(Item.GetType()) == false)
TypesInList.Add(Item.GetType());
}
// we're going to add a string array, so add these two types
if (TypesInList.Contains(typeof(String)) == false)
TypesInList.Add(typeof(String));
if (TypesInList.Contains(typeof(String[])) == false)
TypesInList.Add(typeof(String[]));
// Convert the list of types to a string array of type names
// so we can add it to the ArrayList
String[] TypeNamesArray = new String[TypesInList.Count];
for (int i = 0; i < TypesInList.Count; i++)
TypeNamesArray[i] = ((Type)TypesInList[i]).AssemblyQualifiedName;
// add the array of type names to the ArrayList
ArrayListIn.Add(TypeNamesArray);
// The Serialize function require an array of type objects
Type[] Types = (Type[])TypesInList.ToArray(typeof(Type));
// create the serializer, give it the ArrayList type and
// the array of types in the ArrayList
XmlSerializer Serializer = new XmlSerializer(typeof(ArrayList), Types);
// create a stream for the Serializer output
StringWriter Writer = new StringWriter();
// Do the Serialization
Serializer.Serialize(Writer, ArrayListIn);
String XMLString = Writer.ToString();
// remove the string array of types we added.
// we don't want to affect the original list.
ArrayListIn.RemoveAt(ArrayListIn.Count - 1);
return XMLString;
}
/// ---- DeSerializeArrayList --------------------------------------
/// <summary>
/// Deserialize an XML string to an ArrayList that was
/// serialized with the SerializeArrayList function.
/// </summary>
/// <param name="XmlString">XML String to deserialize</param>
/// <returns>A populated ArrayList</returns>
public static ArrayList DeSerializeArrayList(String XmlString)
{
// extract the array of type names
// it's the last node of the ArrayList
Type[] TempTypes = new Type[2];
TempTypes[0] = typeof(System.String);
TempTypes[1] = typeof(System.String[]);
XmlSerializer Serializer = new XmlSerializer(typeof(ArrayList), TempTypes);
ArrayList MyArrayList;
// create a stream for the DeSerializer input
StringReader StrReader = new StringReader(XmlString);
// first deserialization (we'll do this again)
MyArrayList = Serializer.Deserialize(StrReader) as ArrayList;
// get the array of type names (last node in ArrayList)
String[] StrTypes = MyArrayList[MyArrayList.Count - 1] as String[];
// convert it to an array of type objects
TempTypes = new Type[StrTypes.Length];
for (int i = 0; i < TempTypes.Length; i++)
TempTypes[i] = Type.GetType(StrTypes[i]);
// create the serializer, this time with a complete array
// of the types contained in it.
Serializer = new XmlSerializer(typeof(ArrayList), TempTypes);
// create a stream for the DeSerializer output
StrReader = new StringReader(XmlString);
// Deserialize
MyArrayList = Serializer.Deserialize(StrReader) as ArrayList;
// get rid of the array of type names, we put it
// there and it's not part of the orginal ArrayList
MyArrayList.RemoveAt(MyArrayList.Count - 1);
return MyArrayList;
}
I put the functions in a namespace called XMLUtils (in a file called XMLUtils.cs). You can download the file here. It also includes updated versions of the two functions to serialize and deserialize single objects from an earlier post.
Note: The classes and namespaces of the objects in the ArrayList must be available in the scope of where the serialization and deserialization takes place.
Note: I initially tried to serialize an array of Types instead of an array of Type Names. But the serializer did not like that. It didn't like it one bit.
Note: I could have created the functions as extension methods or added an extension method wrapper like this:
public static string ExSerializeArrayList(this ArrayList ArrayListIn)
{
return SerializeArrayList(ArrayListIn);
}
But, to be symmetric, an Extension method would need to be created for the String class and that would burden the often-used String class with a very specialized and rare usage… so it was not done.
I hope someone finds this useful.
Thanks,
Steve Wellens