How to convert an XML Document to a Dynamic Object in .NET

Sometimes we don’t have a choice on what data we work with, where it’s from or what format we have to deal with. This, in a way, can effect the time take to develop a solution.

In this case we will be looking at XML Data, and how to effortlessly convert that data to a dynamic object which we can easily work with.

You can download the solution from here

Step #1: Let’s define a slightly complex data file.

The data structure contains a simple list of people, to add some complexity, I’ve added a node called “Children” which contains a list of people as well.

<?xml version="1.0" encoding="utf-8" ?>
<People>
  <person Id="1">
    <Name>Waseem</Name>
    <Age>25</Age>
  </person>    
  <person Id="2">
    <Name>John</Name>
    <Age>35</Age>
  </person>    
  <person Id="3">
    <Name>Lucy</Name>
    <Age>21</Age>
    <Children>
      <person Id="5">
        <Name>Jane</Name>
        <Age>4</Age>
      </person>
    </Children>
  </person>    
  <person Id="4">
    <Name>Nikki</Name>
    <Age>32</Age>
  </person>    
</People>

Step #2: extend the XElement object
I’ve decided to extend the XElement object itself to simply future implementations.
this will touch on two extra (important) topics:

  1. Extension Methods – Where we extend XElement
  2. Recursion – to cater for Child lists
  3. Expandos – some cool .NET magic that allows us to add properties at runtime

Thinking about the code required to satisfy our object, we need to cater for the following cases:

  1. Attributes on the current node that are properties
  2. Child nodes in the current node that are properties
  3. Child nodes in the current node that are more complex properties (such as the list of children)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Dynamic;

namespace Expr
{
    // XElement extensions
    public static class XElementExtensions
    {
        // extended the XElement with a method called DoDynamicList
        public static List<dynamic> ToDynamicList(this XElement elements, List<dynamic> data = null)
        {
            // if we already have items in the data object, we will append to them
            // if not create a new data object
            if(data == null)
            {
                data = new List<dynamic>();
            }

            // loop through child elements
            foreach (XElement element in elements.Elements())
            {
                // define an Expando Dynamic
                dynamic person = new ExpandoObject();

                // cater for attributes as properties
                if (element.HasAttributes)
                {
                    foreach (var attribute in element.Attributes())
                    {
                        ((IDictionary<string, object>)person).Add(attribute.Name.LocalName, attribute.Value);
                    }
                }

                // cater for child nodes as properties, or child objects
                if (element.HasElements)
                {
                    foreach (XElement subElement in element.Elements())
                    {
                        // if sub element has child elements
                        if (subElement.HasElements)
                        {
                            // using a bit of recursion lets us cater for an unknown chain of child elements
                            ((IDictionary<string, object>)person).Add(subElement.Name.LocalName, subElement.ToDynamicList());
                        }
                        else
                        {
                            ((IDictionary<string, object>)person).Add(subElement.Name.LocalName, subElement.Value);
                        }
                    }
                }

                data.Add(person);
            }

            return data;
        }
    }
}

Step #3: Implement
Once we have the extension method in play, the implementation is a simple.
I’ve created a console application to demonstrate this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Dynamic;

namespace Expr
{
    class Program
    {
        static void Main(string[] args)
        {
            // get data file path
            string dataPath = MapPath("SampleData.xml");

            // read into stream
            using(StreamReader reader = new StreamReader(dataPath))
            {
                // load into XElement
                XElement doc = XElement.Load(reader);

                // using our ToDynamicList (Extenion Method)
                var people = doc.ToDynamicList();

                // loop through each person
                foreach(dynamic person in people)
                {
                    Console.WriteLine("id:\t" + person.Id);
                    Console.WriteLine("Name:\t" + person.Name);
                    Console.WriteLine("Age:\t" + person.Age);
                    Console.WriteLine("----------------------------------");
                    try
                    {
                        // loop through children, if any
                        foreach(dynamic child in person.Children)
                        {
                            Console.WriteLine("\tid:\t" + child.Id);
                            Console.WriteLine("\tName:\t" + child.Name);
                            Console.WriteLine("\tAge:\t" + child.Age);
                        }
                        Console.WriteLine("----------------------------------");
                    }
                    catch(Exception ex)
                    {
                    }
                }

            }

            // end...
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }

        // Maps a path from the execution folder
        static string MapPath(string path)
        {
            return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), path);
        }
    }
}

And here’s our simple sample output:

id:     1
Name:   Waseem
Age:    25
----------------------------------
id:     2
Name:   John
Age:    35
----------------------------------
id:     3
Name:   Lucy
Age:    21
----------------------------------
        id:     5
        Name:   Jane
        Age:    4
----------------------------------
id:     4
Name:   Nikki
Age:    32
----------------------------------
Press any key to exit

We can even read this with LINQ!!!!

                foreach (dynamic person in people.Where(X => X.Name == "Waseem"))
                {
                    Console.WriteLine("id:\t" + person.Id);
                    Console.WriteLine("Name:\t" + person.Name);
                    Console.WriteLine("Age:\t" + person.Age);
                    Console.WriteLine("----------------------------------");
                    try
                    {
                        // loop through children, if any
                        foreach(dynamic child in person.Children)
                        {
                            Console.WriteLine("\tid:\t" + child.Id);
                            Console.WriteLine("\tName:\t" + child.Name);
                            Console.WriteLine("\tAge:\t" + child.Age);
                        }
                        Console.WriteLine("----------------------------------");
                    }
                    catch(Exception ex)
                    {
                    }
                }

here’s the output:

id:     1
Name:   Waseem
Age:    25
----------------------------------
Press any key to exit

You can download the solution from here