Monday, September 13, 2010

How To: Serialize Objects to Xml using LINQ

Serializing Objects to Xml can be done in numerous ways,
for example by using the System.Xml.Serialization.XmlSerializer.
Most of these performs only shallow serialization and in return you get your basic Xml.

In this post I will try and explain how easy it is to write your own Xml serializer using LINQ and Reflection.
The code sample below can create en read xml files from any Object or List of Objects and it also adds an Xml comment line.

Let me present the code, I'll go over it afterwards:


#region Copyright © 2010 Ruben Knuijver [r.knuijver@primecoder.com]
/*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the author(s) be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.ComponentModel;

public class ObjectXml
{

public static XElement ObjectToElement(XName name, object obj)
{
if (obj == null)
throw new ArgumentNullException("obj");

Type type = obj.GetType();

var elems = from property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)
let value = property.GetValue(obj, null)
where property.CanRead == true
select new XAttribute(property.Name, value);

return new XElement(name, elems);
}

public static void SaveListAsXml(List list, string filename)
{
var elements = from item in list
select ObjectToElement("Item", item);

XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("List - XmlExport by R.F. Knuijver"),
new XElement(typeof(T).Name,
new XAttribute("version", "1.0"), elements.ToArray()));

doc.Save(filename);
}

public static T ElementToObject(XElement element)
{
T value = Activator.CreateInstance();
var properties = from a in typeof(T).GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)
where a.CanWrite
select a;

foreach (var item in properties)
item.SetValue(value, ChangeType(element.Attribute(item.Name).Value, item.PropertyType), null);

return value;
}
public static List LoadListFromXml(string filename)
{
var doc = XDocument.Load(filename);
var elements = from e in doc.Element(typeof(T).Name).Elements()
select ElementToObject(e);

return elements.ToList();
}

public static object ChangeType(object value, Type conversionType)
{
return ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
}
public static object ChangeType(object value, Type conversionType, IFormatProvider provider)
{
if (conversionType == null)
throw new ArgumentNullException("conversionType");


if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (value == null)
return null;

// It's a nullable type, and not null, so that means it can be converted to its underlying type
// so overwrite the passed-in conversion type with this underlying type
NullableConverter nullableConverter = new NullableConverter(conversionType);
conversionType = nullableConverter.UnderlyingType;
}

// deal with conversion to enum types when input is a string
if (conversionType.IsEnum && value is string)
return Enum.Parse(conversionType, value as string);

// deal with conversion to enum types when input is a integral primitive
if (value != null && conversionType.IsEnum && value.GetType().IsPrimitive && !(value is bool) && !(value is char) && !(value is float) && !(value is double))
return Enum.ToObject(conversionType, value);

// pass the call on to Convert.ChangeType
return Convert.ChangeType(value, conversionType, provider);
}

public static T ChangeType(this object value)
{
if (value == null || value is DBNull)
return default(T);

return (T)ChangeType(value, typeof(T));
}
}