47

Is it possible Deserialize unknown XML to object like below?

 var xml = @"<Students><Student><Name>Arul</Name><Mark>90</Mark></Student></Students>";

 var serializer = new XmlSerializer(typeof(DynamicObject));

 dynamic students = serializer.Deserialize(new XmlTextReader(new StringReader(xml)));
abatishchev
  • 98,240
  • 88
  • 296
  • 433
user1875919
  • 483
  • 1
  • 5
  • 4

1 Answers1

80

You may want to try this.

string xml = @"<Students>
                <Student ID=""100"">
                    <Name>Arul</Name>
                    <Mark>90</Mark>
                </Student>
                <Student>
                    <Name>Arul2</Name>
                    <Mark>80</Mark>
                </Student>
            </Students>";

dynamic students = DynamicXml.Parse(xml);

var id = students.Student[0].ID;
var name1 = students.Student[1].Name;

foreach(var std in students.Student)
{
    Console.WriteLine(std.Mark);
}

public class DynamicXml : DynamicObject
{
    XElement _root;
    private DynamicXml(XElement root)
    {
        _root = root;
    }

    public static DynamicXml Parse(string xmlString)
    {
        return new DynamicXml(XDocument.Parse(xmlString).Root);
    }

    public static DynamicXml Load(string filename)
    {
        return new DynamicXml(XDocument.Load(filename).Root);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;

        var att = _root.Attribute(binder.Name);
        if (att != null)
        {
            result = att.Value;
            return true;
        }

        var nodes = _root.Elements(binder.Name);
        if (nodes.Count() > 1)
        {
            result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
            return true;
        }

        var node = _root.Element(binder.Name);
        if (node != null)
        {
            result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
            return true;
        }

        return true;
    }
}

--EDIT--

To make it work with xml namespaces, I added RemoveNamespaces method.

public class DynamicXml : DynamicObject
{
    XElement _root;
    private DynamicXml(XElement root)
    {
        _root = root;
    }

    public static DynamicXml Parse(string xmlString)
    {
        return new DynamicXml(RemoveNamespaces(XDocument.Parse(xmlString).Root));
    }

    public static DynamicXml Load(string filename)
    {
        return new DynamicXml(RemoveNamespaces(XDocument.Load(filename).Root));
    }

    private static XElement RemoveNamespaces(XElement xElem)
    {
        var attrs = xElem.Attributes()
                    .Where(a => !a.IsNamespaceDeclaration)
                    .Select(a => new XAttribute(a.Name.LocalName, a.Value))
                    .ToList();

        if (!xElem.HasElements)
        {
            XElement xElement = new XElement(xElem.Name.LocalName, attrs);
            xElement.Value = xElem.Value;
            return xElement;
        }

        var newXElem = new XElement(xElem.Name.LocalName, xElem.Elements().Select(e => RemoveNamespaces(e)));
        newXElem.Add(attrs);
        return newXElem;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;

        var att = _root.Attribute(binder.Name);
        if (att != null)
        {
            result = att.Value;
            return true;
        }

        var nodes = _root.Elements(binder.Name);
        if (nodes.Count() > 1)
        {
            result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
            return true;
        }

        var node = _root.Element(binder.Name);
        if (node != null)
        {
            result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
            return true;
        }

        return true;
    }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • 8
    This works great if multiple elements are returned by fails of only a single element is returned and you are using it in a foreach! I would suggest changing "result = new DynamicXml(node);" to "result = new List() { new DynamicXml(node) };" which will return a list with a single element. – Matthew M. Apr 29 '14 at 14:28
  • Setting or getting attribute values is better done with TrySetIndex/TryGetIndex. So if we have an element like the syntax for reaching the name attribute would be person["name"]. – Rezo Megrelidze Aug 24 '14 at 20:13
  • 1
    @RezoMegrelidze But I wanted to implement it as `person.name`. – L.B Aug 25 '14 at 05:57
  • 3
    @L.B elements and attributes are not the same. So having different syntax for each one makes more sense. – Rezo Megrelidze Aug 25 '14 at 12:10
  • 13
    @RezoMegrelidze This is my code, I wanted it that way. Feel free to write your own version according to your needs – L.B Aug 25 '14 at 12:13
  • 1
    @L.B - nicely played sir! – Valamas Apr 09 '15 at 01:25
  • 1
    Bless you @L.B! Put me on the right track! Now I can sleep peacefully :-) Nice to know about DynamicObject internals!! – Sudhanshu Mishra Nov 24 '15 at 11:51
  • 4
    Impressive. This is so useful for tacking onto an API where the response has a lot of gumph and I am just interested in 2 or 3 fields (elements). The only thing is now that I will lie awake wondering how the hell this black magic works. Thanks. – AntDC Apr 20 '16 at 07:37
  • I needed to add `if (binder.Name == "Value") result = _root.Value;` before the final `return true` in the `DynamicXml.cs` file. In an example of XML like `12`, I was unable to get the Value of each MatchType. This was, of course, nested a few levels in the XML, so maybe that had something to do with it. Thanks for your wonderful submission! – The1nk Nov 15 '16 at 16:03
  • This will not work if the XML document has one or more namespaces. It's necessary to change the element lookup. I have modified your answer as appropriate. – GreatAndPowerfulOz Feb 23 '17 at 16:52
  • 2
    Great but I found one bug - for empty elements with attributes code returns an empty string instead of the element like here ``. I fixed this changing 39 `DynamicXml` class line to this `result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;` – Vasyl Zvarydchuk Jan 04 '18 at 19:37