Serialize with Reflection

SerializeWithReflection class is designed to serialize any type using reflection for audit or logging purpose. This class does NOT implement deserialization. Feel free to customize based on your requirements. SerializeWithReflection uses reflection since XML Serializer is unable to serialize all of the types.

Sometime you may come across a scenario where you would need to serialize a type manually and reflection may be a good choice since you don’t know the types ahead of time. My use case scenario was- to serialize request/response at the .Net Remoting Sink at the server side and most of the types did not have default constructor. Microsoft has improved the serialization many fold in the later version of CLR and DataContractSerializer (or NetDataContractSerializer) can serialize almost anything as long as you define the KnownTypes correctly.

Caution: SerializeWithReflection uses recursive method. I came across a scenario of OutOfMemory where a public property on a object was referencing itself in the get accessor!

SerializeWithReflection.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml.Linq;
using System.Collections;
using System.Reflection;
using System.Xml;

namespace www.aspnet4you.com
{
	/// <summary>
	/// SerializeWithReflection class is designed to serialize any type using reflection for audit or logging purpose.
	/// This class does NOT implement deserialization. Feel free to customize based on your requirements.
	/// 
	/// SerializeWithReflection uses reflection since XML Serializer is unable to serialize objects without default constructor, 
	/// objects that implements IDictionary, objects using generics, etc.
	/// </summary>
	public static class SerializeWithReflection
	{
		private const int maxRecursiveLevel = 10;
		private const int maxRecursionAllowed = 7500;
		private const string dictionaryNotSerialized = "E1000:Unable to serialize Dictionary item value.";
		private const string genericNotSerialized = "E1001:Unable to serialize generic item value.";
		private const string indexedPropertyNotSerialized = "E1002:Indexed property not serialized.";
		private const string propertyNotSerialized = "E1003:Property not serialized.";
		private const string circularRefNotSerialized = "E1004:Circular object reference. Skipping serialization.";
		private const string maxRecursionExceeded = "E1005:Maximum recursion exceeded. Exiting the loop to avoid overflow.";
		private const string byteArraySkipMsg = "E1006:Skipping byte array serialization.";

		public static string Serialize(object obj)
		{
			XDocument doc = new XDocument();

			if (obj != null)
			{
				XElement objElement = SerializeAnyObject(obj);
				doc.Add(objElement);
			}

			return doc.ToString();
		}

		private static XElement SerializeAnyObject(object obj)
		{
			int intRecursion = 0;
			XElement subElement = CreateXml(SanitizeXmlString(obj.GetType().Name), obj, null, ref intRecursion);
			return subElement;
		}


		private static XElement CreateXml(string name, object obj, ObjGraph parentGraph, ref int recursionCount)
		{

			int currentRecursion = recursionCount++;
			ObjGraph curGraph = null;

			var xElement = new XElement(SanitizeXmlString(name));

			if (currentRecursion >= maxRecursionAllowed)
			{
				if (currentRecursion == maxRecursionAllowed)
				{
					xElement.Add(maxRecursionExceeded);
					return xElement;
				}
				else
				{
					//Force Exit to avoid stackoverflow which can't be caught by exception handler.
					//Sorry my friend but recursive call is ending here.
					return null;
				}
			}

			if (obj != null)
			{
				//Keep track of current object hierarchy
				curGraph = new ObjGraph(obj.GetType().FullName);
				curGraph.Parent = parentGraph;
			}

			if (obj == null)
			{
				xElement.Add("");
			}
			else
			{
				Type type = obj.GetType();
				bool isSimpleType = IsSimpleType(type);

				if (isSimpleType == true)
				{
					xElement.Add(obj);
				}
				else if (type == Type.GetType("System.RuntimeType"))
				{
					xElement.Add(type.FullName);
				}
				else if (type.GetInterface("IXmlSerializable") != null)
				{
					///Covers XElement type and more..
					IXmlSerializable xDoc = (IXmlSerializable)obj;
					xElement.Add(xDoc);
				}
				else if (type == typeof(XmlDocument))
				{
					XmlDocument xDoc = (XmlDocument)obj;
					xElement.Add(xDoc.DocumentElement.OuterXml);
				}
				else if (type.IsGenericType == true)
				{
					Type[] typeParameters = type.GetGenericArguments();
					Type typeDictinary = type.GetInterface("IDictionary`2");
					Type typeEnumerable = type.GetInterface("IEnumerable");

					if (typeDictinary != null)
					{
						Type tVal = typeParameters[1];
						string sanitizedTValName = SanitizeXmlString(tVal.Name);

						IEnumerable keys = (IEnumerable)typeDictinary.GetProperty("Keys").GetValue(obj, null);
						PropertyInfo itemProperty = typeDictinary.GetProperty("Item");

						if (keys != null && itemProperty != null)
						{
							foreach (object key in keys)
							{
								object value = null;

								try
								{
									value = itemProperty.GetValue(obj, new object[] { key });
								}
								catch
								{
									value = dictionaryNotSerialized;
								}

								if (AddedChildElement(ref xElement, sanitizedTValName, value, true) == false)
								{
									xElement.Add(CreateXml(sanitizedTValName, value, curGraph, ref currentRecursion));
								}
							}
						}
					}
					else if (typeEnumerable != null)
					{
						Type tParam = typeParameters[0];
						string sanitizedTParamName = SanitizeXmlString(tParam.Name);
						IEnumerable enumerableObject = obj as IEnumerable;

						if (enumerableObject != null)
						{
							foreach (object item in enumerableObject)
							{
								if (AddedChildElement(ref xElement, sanitizedTParamName, item, true) == false)
								{
									xElement.Add(CreateXml(sanitizedTParamName, item, curGraph, ref currentRecursion));
								}
							}
						}
					}
					else
					{
						Type tParam = typeParameters[0];

						int count = -1;
						PropertyInfo countProperty = type.GetProperty("Count");
						PropertyInfo itemProperty = type.GetProperty("Item");
						if (countProperty != null && itemProperty != null)
						{
							count = (int)countProperty.GetValue(obj, null);
						}

						if (count > 0)
						{
							string sanitizedTParamName = SanitizeXmlString(tParam.Name);

							for (int item = 0; item < count; item++)
							{
								object propValue = null;
								try
								{
									propValue = itemProperty.GetValue(obj, new object&#91;&#93; { item });
								}
								catch
								{
									propValue = genericNotSerialized;
								}

								if (AddedChildElement(ref xElement, sanitizedTParamName, propValue, true) == false)
								{
									xElement.Add(CreateXml(sanitizedTParamName, propValue, curGraph, ref currentRecursion));
								}
							}
						}
					}
				}
				else if (type.IsArray == true)
				{
					Type elementType = type.GetElementType();
					string sanitizedElementTypeName = SanitizeXmlString(elementType.Name);

					if (elementType == typeof(Byte))
					{
						//We don't want to serialize streams (i.e. images)
						AddedChildElement(ref xElement, sanitizedElementTypeName, byteArraySkipMsg, true);
					}
					else if (elementType.IsPrimitive == true || elementType == typeof(DateTime) || elementType == typeof(string))
					{
						Array objArry = (Array)obj;

						foreach (var aArry in objArry)
						{
							AddedChildElement(ref xElement, sanitizedElementTypeName, aArry, true);
						}
					}
					else
					{
						object&#91;&#93; objArryObjects = (object&#91;&#93;)obj;

						foreach (object oArry in objArryObjects)
						{
							XElement oArryElement = CreateXml(oArry.GetType().Name, oArry, curGraph, ref currentRecursion);
							xElement.Add(oArryElement);
						}
					}
				}
				else if (type == typeof(ArrayList))
				{
					ArrayList objArry = (ArrayList)obj;

					foreach (var aArry in objArry)
					{
						XElement oArryElement = CreateXml(aArry.GetType().Name, aArry, curGraph, ref currentRecursion);
						xElement.Add(oArryElement);
					}
				}
				else
				{
					if (IsCircularReference(curGraph) == true)
					{
						xElement.Add(circularRefNotSerialized);
					}
					else
					{
						foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
						{

							string propName = propertyInfo.Name;
							string sanitizedPropName = SanitizeXmlString(propName);
							ParameterInfo&#91;&#93; parameterInfo = propertyInfo.GetIndexParameters();

							object propValue = null;

							if (parameterInfo.Length > 0)
							{
								if (parameterInfo.Length == 1 && parameterInfo[0].ParameterType == typeof(int))
								{
									int indexCount = 0;

									while (true)
									{
										try
										{
											propertyInfo.GetValue(obj, new object[] { indexCount });
											indexCount++;
										}
										catch
										{
											break;
										}
									}

									for (int itemCount = 0; itemCount < indexCount; itemCount++)
									{
										propValue = propertyInfo.GetValue(obj, new object&#91;&#93; { itemCount });

										if (AddedChildElement(ref xElement, sanitizedPropName, propValue, true) == false)
										{
											xElement.Add(CreateXml(sanitizedPropName, propValue, curGraph, ref currentRecursion));
										}
									}
								}
								else
								{
									//it's not possible to iterate through the non-integer based indexed property since
									//we don't have the knowledge about the underlying storage.
									xElement.Add(new XElement(sanitizedPropName) { Value = indexedPropertyNotSerialized });
								}
							}
							else
							{
								try
								{
									propValue = propertyInfo.GetValue(obj, new object&#91;&#93; { });
								}
								catch
								{
									propValue = propertyNotSerialized;
								}

								if (AddedChildElement(ref xElement, sanitizedPropName, propValue, true) == false)
								{
									xElement.Add(CreateXml(sanitizedPropName, propValue, curGraph, ref currentRecursion));
								}
							}
						}
					}
				}
			}

			return xElement;
		}

		public static bool IsSimpleType(Type type)
		{
			return type.IsPrimitive || type.IsValueType || type == typeof(string) || type == typeof(DateTime);
		}

		/// <summary>
		/// IsCircularReference checks the object graph hierarchy. It loops through the hierarchy
		/// until Parent is null or the object graph found circular reference up to the maxRecursiveLevel (default to 20) level.
		/// </summary>
		/// <param name="graphToInspect"></param>
		/// <returns></returns>
		private static bool IsCircularReference(ObjGraph graphToInspect)
		{
			bool retVal = false;

			if (graphToInspect != null)
			{
				int loopCount = 0;
				string currentObjectName = graphToInspect.Name;
				ObjGraph tempGraph = graphToInspect.Parent;

				while (tempGraph != null)
				{
					if (tempGraph.Parent != null)
					{
						if (currentObjectName == tempGraph.Parent.Name && loopCount >= maxRecursiveLevel)
						{
							retVal = true;
							break;
						}
					}

					tempGraph = tempGraph.Parent;
					loopCount++;
				}
			}


			return retVal;
		}

		private static bool AddedChildElement(ref XElement xElement, string elementName, object elementValue, bool nameSanitized)
		{
			bool retVal = false;
			string sanitizedName = SanitizeXmlString(elementName, nameSanitized);

			if (elementValue == null)
			{
				xElement.Add(new XElement(sanitizedName));
				retVal = true;
			}
			else
			{
				Type elementType = elementValue.GetType();
				if (elementType.IsPrimitive == true)
				{
					xElement.Add(new XElement(sanitizedName) { Value = elementValue.ToString() });
					retVal = true;
				}
				else if (IsSimpleType(elementType) == true)
				{
					xElement.Add(new XElement(sanitizedName) { Value = elementValue.ToString() });
					retVal = true;
				}
			}

			return retVal;
		}

		/// <summary>
		/// Remove illegal XML characters from a string.
		/// See IsLegalXmlElementChar for chars allowed.
		/// </summary>
		public static string SanitizeXmlString(string xml)
		{
			if (xml == null)
			{
				throw new ArgumentNullException("xml");
			}

			StringBuilder buffer = new StringBuilder(xml.Length);

			foreach (char c in xml)
			{
				if (IsLegalXmlElementChar(c))
				{
					buffer.Append(c);
				}
			}

			return buffer.ToString();
		}

		/// <summary>
		/// Remove illegal XML characters from a string.
		/// See IsLegalXmlElementChar for chars allowed.
		/// No further sanitization is done if alreadySanitized is true.
		/// </summary>
		public static string SanitizeXmlString(string xml, bool alreadySanitized)
		{
			string tempVal = xml;

			if (alreadySanitized == false)
			{
				tempVal = SanitizeXmlString(xml);
			}

			return tempVal;
		}

		/// <summary>
		/// Whether a given character is allowed by XElement.
		/// Allowed chars: 0-9, A-Z, a-z, space, dash and underscore.
		/// </summary>
		public static bool IsLegalXmlElementChar(int character)
		{

			return
			(
				(character >= 0x30 && character <= 0x39) || /* 0-9 */
				(character >= 0x41 && character <= 0x5A) || /*A-Z */
				(character >= 0x61 && character <= 0x7A) || /* a-z */
				(character == 0x20) || /* space */
				(character == 0x2D) || /* dash */
				(character == 0x5F)    /* underscore */
			);
		}
	}
}

&#91;/code&#93;

<strong>ObjGraph.cs</strong>
[code language=""csharp"]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace www.aspnet4you.com
{
	public class ObjGraph
	{
		public ObjGraph(string name)
		{
			this.Name = name;
		}
		public ObjGraph Parent { get; set; }
		public string Name { get; set; }
	}
}

Acknowledgement:
http://stackoverflow.com/questions/10252207/how-to-serialize-interface-typed-member

Leave a Reply