Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C# 4.0
Tip/Trick

Converting XML to an Dynamic Object using ExpandoObject

Rate me:
Please Sign up or sign in to vote.
4.85/5 (22 votes)
27 Apr 2020CPOL3 min read 93.9K   52   18
Load an XML document and convert it to a dynamic object
A simple method to convert XML into an dynamic object using ExpandoObject

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

C#
private dynamic GetAnonymousType(string xml, XElement element = null)
{
    // either set the element directly or parse XML from the xml parameter.
    element = string.IsNullOrEmpty(xml) ? element : XDocument.Parse(xml).Root;
            
    // if there's no element than there's no point to continue
    if (element == null) return null;

    IDictionary<string, dynamic> result = new ExpandoObject();
            
    // grab any attributes and add as properties
    element.Attributes().AsParallel().ForAll
         (attribute => result[attribute.Name.LocalName] = attribute.Value);

    // check if there are any child elements.
    if (!element.HasElements) 
    {
        // check if the current element has some value and add it as a property
        if (!string.IsNullOrWhiteSpace(element.Value))
            result[element.Name.LocalName] = element.Value;
        return result;
    }

    // Check if the child elements are part of a collection (array). If they are not then
    // they are either a property of complex type or a property with simple type
    var isCollection = (element.Elements().Count() > 1
                            && element.Elements().All(e => e.Name.LocalName.ToLower() 
                               == element.Elements().First().Name.LocalName.ToLower())
                                        
                            // the pluralizer is needed in a scenario where you have 
                            // 1 child item and you still want to treat it as an array.
                            // If this is not important than you can remove the last part 
                            // of the if clause which should speed up this method considerably.
                            || element.Name.LocalName.ToLower() == 
                               new Pluralize.NET.Core.Pluralizer().Pluralize
                               (element.Elements().First().Name.LocalName).ToLower());

    var values = new ConcurrentBag<dynamic>();

    // check each child element
    element.Elements().ToList().AsParallel().ForAll(i =>
    {
        // if it's part of a collection then add the collection items to a temp variable 
        if (isCollection) values.Add(GetAnonymousType(null, i));
        else
            // if it's not a collection, but it has child elements
            // then it's either a complex property or a simple property
            if (i.HasElements)
                // create a property with the current child elements name 
                // and process its properties
                result[i.Name.LocalName] = GetAnonymousType(null, i);
            else
                // create a property and just add the value
                result[i.Name.LocalName] = i.Value;                       
    });

    // for collection items we want skip creating a property with the child item names, 
    // but directly add the child properties to the
    if (values.Count > 0) result[element.Name.LocalName] = values;


    // return the properties of the processed element
    return result;
}

Example XML

XML
<?xml version="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

C#
dynamic license = GetAnonymousType(xmlString);

// Getting the values from the dynamic object is really easy now.
var registeredUser = license.RegisteredUser;
var companyName = license.Company;

// Getting a collection is just as easy as it simply returns a list
var sites = license.Sites;
foreach(var site in sites)
{
   var host = site.Host;
}

// I am sure it's easy enough for you guys to extrapolate from this simple example.

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseThanks Pin
Member 103160124-Jun-22 0:49
Member 103160124-Jun-22 0:49 
QuestionAttributes for the XML nodes Pin
Nikhil Pakki19-Mar-18 23:32
Nikhil Pakki19-Mar-18 23:32 
QuestionBug on non US English Systems and "Fix" Pin
ndinges11-Aug-14 8:51
ndinges11-Aug-14 8:51 
AnswerRe: Bug on non US English Systems and "Fix" Pin
RemcoReitsma13-Sep-14 9:41
RemcoReitsma13-Sep-14 9:41 
GeneralMy vote of 4 Pin
thund3rstruck7-Aug-14 7:15
thund3rstruck7-Aug-14 7:15 
GeneralMy vote of 5 Pin
Some D00d7-Aug-14 4:35
Some D00d7-Aug-14 4:35 
GeneralReally a good one Pin
vikashevs5-Aug-13 3:24
vikashevs5-Aug-13 3:24 
QuestionSerialization Pin
BnR-Fouress13-Dec-12 1:56
BnR-Fouress13-Dec-12 1:56 
GeneralReally good stuff! but is not taking account of node attribu... Pin
Rinaldo J. Sassaroli13-Feb-12 13:26
Rinaldo J. Sassaroli13-Feb-12 13:26 
GeneralRe: Really good stuff!but is not taking account of node attribu... Pin
alexquisi29-Nov-15 23:28
alexquisi29-Nov-15 23:28 
GeneralRe: Really good stuff!but is not taking account of node attribu... Pin
DaveKolb30-Apr-16 7:45
DaveKolb30-Apr-16 7:45 
GeneralHow do I use the object once created? Like added values into... Pin
Member 83291237-Nov-11 9:14
Member 83291237-Nov-11 9:14 
GeneralRe: I have added a small example to the article which would help... Pin
RemcoReitsma14-Dec-11 16:17
RemcoReitsma14-Dec-11 16:17 
I have added a small example to the article which would help you hopefully
GeneralAre you missing the keyword dynamic in your var p = result... Pin
cechode18-Jul-11 10:13
cechode18-Jul-11 10:13 
GeneralRe: Hmm something got messed up during copy and paste because fo... Pin
RemcoReitsma18-Jul-11 15:49
RemcoReitsma18-Jul-11 15:49 
GeneralRe: Don't forget to vote up if you like the tip ;-) Pin
RemcoReitsma14-Dec-11 19:28
RemcoReitsma14-Dec-11 19:28 
GeneralReason for my vote of 2 code produced a stackover flow excep... Pin
cechode18-Jul-11 6:13
cechode18-Jul-11 6:13 
GeneralRe: I've updated and simplified the code. There was indeed a bug... Pin
RemcoReitsma18-Jul-11 9:02
RemcoReitsma18-Jul-11 9:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.