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