Click here to Skip to main content
Click here to Skip to main content

Converting XML to an Dynamic Object using ExpandoObject

, 11 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Load an XML document and convert it to a dynamic object

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 could Courses. After some Googling, I came across the following little gem.

System.Data.Entity.Design.PluralizationServices; (http://msdn.microsoft.com/en-us/library/system.data.entity.design.pluralizationservices(v=vs.110).aspx)
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.

The Code

private static dynamic _getExpandoFromXml(String file, XElement node = null)
{
    if (String.IsNullOrWhiteSpace(file) && node == null) return null;
            
    // If a file is not empty then load the xml and overwrite node with the
    // root element of the loaded document
    node = !String.IsNullOrWhiteSpace(file) ? XDocument.Load(file).Root : node;

    IDictionary<String, dynamic> result = new ExpandoObject();

    // implement fix as suggested by [ndinges]
    var pluralizationService = 
        PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en-us"));

    // use parallel as we dont really care of the order of our properties
    node.Elements().AsParallel().ForAll(gn =>
    {
        // Determine if node is a collection container
        var isCollection = gn.HasElements  &&
            (
                // if multiple child elements and all the node names are the same
                gn.Elements().Count() > 1 && 
                gn.Elements().All(
                    e => e.Name.LocalName.ToLower() == gn.Elements().First().Name.LocalName) ||
                    
                // if there's only one child element then determine using the PluralizationService if
                // the pluralization of the child elements name matches the parent node. 
                gn.Name.LocalName.ToLower() == pluralizationService.Pluralize(
                    gn.Elements().First().Name.LocalName).ToLower()
            );
                
        // If the current node is a container node then we want to skip adding
        // the container node itself, but instead we load the children elements
        // of the current node. If the current node has child elements then load
        // those child elements recursively
        var items = isCollection ? gn.Elements().ToList() : new List<XElement>() { gn };

        var values = new List<dynamic>();

        // use parallel as we dont really care of the order of our properties
        // and it will help processing larger XMLs
        items.AsParallel().ForAll(i => values.Add((i.HasElements) ? 
           _getExpandoFromXml(null, i) : i.Value.Trim()));

        // Add the object name + value or value collection to the dictionary
        result[gn.Name.LocalName] = isCollection ? values : values.FirstOrDefault();
    });
    return result;
}

Example 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

    dynamic license = _getExpandoFromXml('path to xml file');
    
    // 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.

License

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

Share

About the Author

RemcoReitsma

Netherlands Netherlands
No Biography provided

Comments and Discussions

 
QuestionBug on non US English Systems and "Fix" Pinmemberndinges11-Aug-14 9:51 
AnswerRe: Bug on non US English Systems and "Fix" PinmemberRemcoReitsma13-Sep-14 10:41 
GeneralMy vote of 4 Pinmemberthund3rstruck7-Aug-14 8:15 
GeneralMy vote of 5 PinmemberSome D00d7-Aug-14 5:35 
GeneralReally a good one Pinmembervikashevs5-Aug-13 4:24 
QuestionSerialization PinmemberBnR-Fouress13-Dec-12 2:56 
GeneralReally good stuff! but is not taking account of node attribu... PinmemberRinaldo J. Sassaroli13-Feb-12 14:26 
GeneralHow do I use the object once created? Like added values into... PinmemberMember 83291237-Nov-11 10:14 
GeneralRe: I have added a small example to the article which would help... PinmemberRemcoReitsma14-Dec-11 17:17 
GeneralAre you missing the keyword dynamic in your var p = result... Pinmembercechode18-Jul-11 11:13 
GeneralRe: Hmm something got messed up during copy and paste because fo... PinmemberRemcoReitsma18-Jul-11 16:49 
GeneralRe: Don't forget to vote up if you like the tip ;-) PinmemberRemcoReitsma14-Dec-11 20:28 
GeneralReason for my vote of 2 code produced a stackover flow excep... Pinmembercechode18-Jul-11 7:13 
GeneralRe: I've updated and simplified the code. There was indeed a bug... PinmemberRemcoReitsma18-Jul-11 10:02 
I've updated and simplified the code.
There was indeed a bug when it hit the <tables> node because the skip variable was not set to true. Also the code was too messy and had a redundant while loop.
 
It looks to be working okay now, so have another try Smile | :)

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 11 Aug 2014
Article Copyright 2011 by RemcoReitsma
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid