Skip to content

Instantly share code, notes, and snippets.

@vladris
Created December 3, 2017 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vladris/d3d9c7ab5ba790f7928997f6b1b6cb21 to your computer and use it in GitHub Desktop.
Save vladris/d3d9c7ab5ba790f7928997f6b1b6cb21 to your computer and use it in GitHub Desktop.
Extension method for XElement to deserialize into a dynamic object
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Xml.Linq;
/// <summary>
/// Provides extension method for XElement to deserialize to a dynamic object
///
/// The element's attributes will be stored as properties on the dynamic object.
/// The child elements will also be stored as properties on the dynamic object with the
/// following convention: if the element has a single child with a given name, that
/// child will appear as a property with the element's name. If multiple children have
/// the same name, they will be stored as a List of objects in a property with their name.
///
/// By default, the name of the element will be stored into the Name property, the value
/// of the element will be stored into the Value property, and a method to enumerate over
/// children will be provided as Elements(). The names can be customized if they conflict
/// with the content of the XML.
/// </summary>
static class XElementDynamicDeserializer
{
/// <summary>
/// Deserializes XElement into a dynamic object.
/// </summary>
/// <param name="element">XElement to deserialize</param>
/// <param name="nameProperty">Property name for the name of the element</param>
/// <param name="valueProperty">Property name for the value of the element</param>
/// <param name="elementsMethod">Method name for the method to enumerate child elements</param>
/// <returns>Dynamic object</returns>
public static dynamic ToDynamic(
this XElement element,
string nameProperty = "Name",
string valueProperty = "Value",
string elementsMethod = "Elements")
{
var result = new ExpandoObject() as IDictionary<string, object>;
// Node name and value
result[nameProperty] = element.Name.LocalName;
result[valueProperty] = element.Value;
// Add each attribute to the expando object
foreach (var attribute in element.Attributes())
{
result[attribute.Name.LocalName] = attribute.Value;
}
// Children element list (to be populated) and method to enumerate over them
var children = new List<dynamic>();
result[elementsMethod] = new Func<IEnumerable<dynamic>>(() => children);
// Add each child node to the expando object and children list
foreach (var child in element.Elements())
{
var node = ToDynamic(child, nameProperty, valueProperty, elementsMethod);
children.Add(node);
var nodeName = (node as IDictionary<string, object>)[nameProperty].ToString();
// If we haven't seen the node name before, add it as a property
if (!result.ContainsKey(nodeName))
{
result[nodeName] = node;
continue;
}
// If we've seen it once, replace with a list
if (!(result[nodeName] is List<dynamic>))
{
result[nodeName] = new List<dynamic>() { result[nodeName] };
}
(result[nodeName] as List<dynamic>).Add(node);
}
return result;
}
}
@vladris
Copy link
Author

vladris commented Dec 3, 2017

Example:

const string xml =
    @"<Cars>
        <Car Color=""Red"">
          <Make>Tesla</Make>
          <Model>S</Model>
        </Car>
        <Car Color=""Silver"">
          <Make>Tesla</Make>
          <Model>3</Model>
        </Car>
      </Cars>"; 

var cars = XElement.Parse(xml).ToDynamic();

Console.WriteLine($"{cars.Car[1].Color} {cars.Car[1].Make.Value} {cars.Car[1].Model.Value}");    
// Silver Testla 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment