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:
- Extension Methods – Where we extend XElement
- Recursion – to cater for Child lists
- 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:
- Attributes on the current node that are properties
- Child nodes in the current node that are properties
- 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