Introduction
I started looking on the Internet to find a straightforward way of loading in an XML file and converting it to an object on the fly. My goal was to dynamically load setting files in XML format for a web project I'm working on.
As the definition of the XML schema is very different per setup, I thought of using the dynamic
keyword to load an XML document and access it as a simple object. After some browsing, I found some examples that used the ExpandoObject
. Unfortunately, they all either write about converting an object to XML or how to use XML-to-LINQ to map a specific dynamic class/object.
So I ended up writing a small recursive method that loads an XML document and converts it to a simple object.
* UPDATE
I've noticed that even after 3 years, people are still finding this tip useful.
I thought it would be a good idea to share a small update which removes the need for a special type attribute to determine if a child node is part of a collection.
I suddenly realized that the feature I so hated and dreaded in Entity Framework could well be the solution I was looking for to remove the need to decorate my nodes with type=list attributes.
The feature I am talking about is pluralization. It's where in EF, we have a model with a class called Course
which automagically changed in a database table called Courses
. After some Googling, I came across the following little gem:
It allows for a string
to be pluralized or singularized. Then, I thought that this would fit very well in determining if a child element is part of a collection because often, if not always, the parent node name is a pluralization of its children's name.
Anyway, I've changed the logic that it will determine if a node is a container item if it has child elements that meet the following conditions:
- The item has 1 child element and its node name is a singularization of its parents name, or
- The item has multiple child elements, but the child node names must all be the same.
I believe this will cover most of the scenarios and removes any need to have special type=list attributes.
* UPDATE 2
I cannot believe we are now 6 years further and this article is still being used.
This week, I was updating some code for a product feed parser I wrote and I actually had some time
to update this function quite a bit.
For one, it is now .NET Standard so it can be used in .NET Framework 4.5 and higher, as well as .NET Core.
I think the new method also is a lot more useful for multiple scenarios.
Well, I hope people will find it useful.
The Code
private dynamic GetAnonymousType(string xml, XElement element = null)
{
element = string.IsNullOrEmpty(xml) ? element : XDocument.Parse(xml).Root;
if (element == null) return null;
IDictionary<string, dynamic> result = new ExpandoObject();
element.Attributes().AsParallel().ForAll
(attribute => result[attribute.Name.LocalName] = attribute.Value);
if (!element.HasElements)
{
if (!string.IsNullOrWhiteSpace(element.Value))
result[element.Name.LocalName] = element.Value;
return result;
}
var isCollection = (element.Elements().Count() > 1
&& element.Elements().All(e => e.Name.LocalName.ToLower()
== element.Elements().First().Name.LocalName.ToLower())
|| element.Name.LocalName.ToLower() ==
new Pluralize.NET.Core.Pluralizer().Pluralize
(element.Elements().First().Name.LocalName).ToLower());
var values = new ConcurrentBag<dynamic>();
element.Elements().ToList().AsParallel().ForAll(i =>
{
if (isCollection) values.Add(GetAnonymousType(null, i));
else
if (i.HasElements)
result[i.Name.LocalName] = GetAnonymousType(null, i);
else
result[i.Name.LocalName] = i.Value;
});
if (values.Count > 0) result[element.Name.LocalName] = values;
return result;
}
Example XML
="1.0"
<License>
<RegisteredUser>Remco Reitsma</RegisteredUser>
<Company>Xtraworks.com</Company>
<Sites>
<Site>
<Host>xtraworks.com</Host>
<ExpireDate>15/12/2099</ExpireDate>
</Site>
<Site>
<Host>test.com</Host>
<ExpireDate>15/12/2099</ExpireDate>
</Site>
</Sites>
<Modules>
<Module>
<Name>SEO Package</Name>
<Controller>SEO</Controller>
<Version>0.0.1</Version>
<Tables>
<Table>
<Name>SEO_Site</Name>
</Table>
<Table>
<Name>SEO_Pages</Name>
</Table>
</Tables>
</Module>
</Modules>
</License>
Usage
dynamic license = GetAnonymousType(xmlString);
var registeredUser = license.RegisteredUser;
var companyName = license.Company;
var sites = license.Sites;
foreach(var site in sites)
{
var host = site.Host;
}
Note
One thing that is important to realize is that this is a very simple example and is by no means fool-proof. The code as it is would be useful in cases where you have control over the XML files that are provided. Also, the use of dynamic
has some performance issues as internally it will need to do a lot of reflection during run time and you will also lose out on error checking of the dynamic objects during compiling.
It could be useful for example to read in a configuration file or a simple list of products.
History
- 18th July, 2011: Initial version