Click here to Skip to main content
11,573,618 members (49,627 online)
Click here to Skip to main content
Articles » Languages » XML » Serializing » Downloads
Add your own
alternative version

XML Serialization of Complex .NET Objects

, 19 Oct 2008 CPOL 76.3K 2.1K 59
Yet another custom XML serializer, with a slightly different approach.
using System;
using System.Collections.Generic;
using System.Xml;
using System.Reflection;
using System.Xml.Serialization;
using System.Collections;
using System.Globalization;
using System.Threading;
using System.Text;
using System.IO;

namespace CommonLibrary
{
    public class CustomXmlSerializer : CustomXmlSerializerBase
    {   
        Dictionary<Type, TypeInfo> typeCache = new Dictionary<Type, TypeInfo>();        
        Dictionary<Type, IDictionary<ObjKeyForCache, ObjInfo>> objCache = new Dictionary<Type, IDictionary<ObjKeyForCache, ObjInfo>>();
        int objCacheNextId = 0;
        SerializationOptions options;

        protected CustomXmlSerializer(SerializationOptions opt)
        {
            options = opt;
        }        

        void SetTypeInfo(Type objType, XmlElement element)
        {
            if (!options.UseTypeCache)
            {
                // add detailed type information
                WriteTypeToNode(element, objType);
                return;
            }
            TypeInfo typeInfo;
            if (typeCache.TryGetValue(objType, out typeInfo))
            {
                XmlElement onlyElement = typeInfo.OnlyElement;
                if (onlyElement != null)
                {
                    // set the type of the element to be a reference to the type ID
                    // since the element is no longer the only one of this type                    
                    typeInfo.WriteTypeId(onlyElement);                    
                    onlyElement.RemoveAttribute("type");
                    onlyElement.RemoveAttribute("assembly");
                    typeInfo.OnlyElement = null;
                }
                typeInfo.WriteTypeId(element);                
            }
            else
            {
                // add type to cache
                typeInfo = new TypeInfo();
                typeInfo.TypeId = typeCache.Count;
                typeInfo.OnlyElement = element;
                typeCache.Add(objType, typeInfo);
                // add detailed type information
                WriteTypeToNode(element, objType);
            }            
        }

        static void WriteTypeToNode(XmlElement element, Type objType)
        {
            element.SetAttribute("type", objType.FullName);
            element.SetAttribute("assembly", objType.Assembly.FullName);
        }

        XmlElement GetTypeInfoNode()
        {
            XmlElement element = doc.CreateElement("TypeCache");
            foreach (KeyValuePair<Type, TypeInfo> kv in typeCache)
            {
                if (kv.Value.OnlyElement == null)
                {
                    // there is more than one element having this type
                    XmlElement e = doc.CreateElement("TypeInfo");
                    kv.Value.WriteTypeId(e);                    
                    WriteTypeToNode(e, kv.Key);
                    element.AppendChild(e);
                }
            }
            return element.HasChildNodes ? element : null;
        }

        public static XmlDocument Serialize(object obj, int ver, string rootName)
        {
            // determine serialization options
            SerializationOptions serOptions = new SerializationOptions();
            if (obj != null)
            {
                Type objType = obj.GetType();
                object[] attribs = objType.GetCustomAttributes(typeof(CustomXmlSerializationOptionsAttribute), false);
                if (attribs.Length > 0)
                {
                    serOptions = ((CustomXmlSerializationOptionsAttribute)attribs[0]).SerializationOptions;
                }
            }
            // create serializer
            CustomXmlSerializer serializer = new CustomXmlSerializer(serOptions);
            XmlElement element = serializer.SerializeCore(rootName, obj);
            element.SetAttribute("version", ver.ToString());
            element.SetAttribute("culture", Thread.CurrentThread.CurrentCulture.ToString());            
            // add typeinfo
            XmlElement typeInfo = serializer.GetTypeInfoNode();
            if (typeInfo != null)
            {
                element.PrependChild(typeInfo);
                element.SetAttribute("hasTypeCache", "true");
            }
            // add serialized data
            serializer.doc.AppendChild(element);
            return serializer.doc;
        }        

        bool AddObjToCache(Type objType, object obj, XmlElement element)
        {
            ObjKeyForCache kfc = new ObjKeyForCache(obj);
            IDictionary<ObjKeyForCache, ObjInfo> entry;            
            if (objCache.TryGetValue(objType, out entry))
            {                
                // look for this particular object                
                ObjInfo objInfoFound;
                if (entry.TryGetValue(kfc, out objInfoFound))
                {
                    // the object has already been added
                    if (objInfoFound.OnlyElement != null)
                    {
                        objInfoFound.WriteObjId(objInfoFound.OnlyElement);
                        objInfoFound.OnlyElement = null;
                    }
                    // write id to element
                    objInfoFound.WriteObjId(element);
                    return false;
                }                
            }
            else
            {
                // brand new type in the cache
                entry = new Dictionary<ObjKeyForCache, ObjInfo>(1);
                objCache.Add(objType, entry);
            }
            // object not found, add it
            ObjInfo objInfo = new ObjInfo();
            objInfo.Id = objCacheNextId;
            objInfo.OnlyElement = element;
            entry.Add(kfc, objInfo);
            objCacheNextId++;
            return true;
        }

        static bool CheckForcedSerialization(Type objType)
        {
            object[] attribs = objType.GetCustomAttributes(typeof(XmlSerializeAsCustomTypeAttribute), false);
            return attribs.Length > 0;
        }

        XmlElement SerializeCore(string name, object obj)
        {
            XmlElement element = doc.CreateElement(name);
            if (obj == null)
            {
                element.SetAttribute("value", "null");
                return element;
            }

            Type objType = obj.GetType();

            if (objType.IsClass && objType != typeof(string))
            {
                // check if we have already serialized this object
                if (options.UseGraphSerialization && !AddObjToCache(objType, obj, element))
                {
                    return element;
                }                
                // the object has just been added                
                SetTypeInfo(objType, element);

                if (CheckForcedSerialization(objType))
                {
                    // serialize as complex type
                    SerializeComplexType(obj, element);
                    return element;
                }

                IXmlSerializable xmlSer = obj as IXmlSerializable;
                if (xmlSer == null)
                {
                    // does not know about automatic serialization
                    IEnumerable arr = obj as IEnumerable;
                    if (arr == null)
                    {
                        SerializeComplexType(obj, element);
                    }
                    else
                    {
                        foreach (object arrObj in arr)
                        {
                            XmlElement e = SerializeCore(name, arrObj);
                            element.AppendChild(e);
                        }
                    }
                }
                else
                {
                    // can perform the serialization itself
                    StringBuilder sb = new StringBuilder();
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.ConformanceLevel = ConformanceLevel.Fragment;
                    settings.Encoding = Encoding.UTF8;
                    settings.OmitXmlDeclaration = true;
                    XmlWriter wr = XmlWriter.Create(sb, settings);
                    wr.WriteStartElement("value");
                    xmlSer.WriteXml(wr);
                    wr.WriteEndElement();                    
                    wr.Close();

                    element.InnerXml = sb.ToString();
                }
            }
            else
            {
                // the object has just been added                
                SetTypeInfo(objType, element);

                if (CheckForcedSerialization(objType))
                {
                    // serialize as complex type
                    SerializeComplexType(obj, element);
                    return element;
                }
                
                if (objType.IsEnum)
                {
                    object val = Enum.Format(objType, obj, "d");
                    element.SetAttribute("value", val.ToString());
                }
                else
                {
                    if (objType.IsPrimitive || objType == typeof(string) || 
                        objType == typeof(DateTime) || objType == typeof(decimal))
                    {
                        element.SetAttribute("value", obj.ToString());
                    }
                    else
                    {
                        // this is most probably a struct
                        SerializeComplexType(obj, element);
                    }
                }                    
            }

            return element;
        }

        void SerializeComplexType(object obj, XmlElement element)
        {
            Type objType = obj.GetType();
            // get all instance fields
            IDictionary<string, FieldInfo> fields = GetTypeFieldInfo(objType);
            foreach (KeyValuePair<string, FieldInfo> kv in fields)                
            {                    
                // serialize field
                XmlElement e = SerializeCore(kv.Key, kv.Value.GetValue(obj));
                element.AppendChild(e);
            }
        }        

        class ObjInfo
        {
            internal int Id;
            internal XmlElement OnlyElement;

            internal void WriteObjId(XmlElement element)
            {
                element.SetAttribute("id", Id.ToString());
            }
        }

        struct ObjKeyForCache : IEquatable<ObjKeyForCache>
        {
            object m_obj;

            public ObjKeyForCache(object obj)
            {
                m_obj = obj;
            }

            public bool Equals(ObjKeyForCache other)
            {
                return object.ReferenceEquals(m_obj, other.m_obj);
            }
        }

        public class SerializationOptions
        {
            public bool UseTypeCache = true;
            public bool UseGraphSerialization = true;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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

Share

About the Author

No Biography provided

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150624.2 | Last Updated 20 Oct 2008
Article Copyright 2008 by Antoniu-Gabriel Rozsa
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid