1

I am having a requirement where I want to create a class that can deserialize XML with dynamic number of elements. For example:

<root>
   <Element1>text</Element1>
   <Element2>text</Element2>
   <Element3>text</Element3>
   ....
   ...
   <Element10>text</Element10>
</root>

Now, in above XML first 3 elements are mandatory and their element name is known. But after that I don't know the number of elements XML has and name of those elements are also unknown.

How to write a class so that I can deserialize this & read it?

jps
  • 20,041
  • 15
  • 75
  • 79
Harshil Doshi
  • 3,497
  • 3
  • 14
  • 37
  • 1
    Perhaps this related article will help you: (https://stackoverflow.com/questions/13704752/deserialize-xml-to-object-using-dynamic) – Ryan Wilson Aug 26 '20 at 13:19
  • @RyanWilson Thank you. That was really helpful. I got how to read the xml, but In my case I don' know the name of Elements. I am not sure how to find that. – Harshil Doshi Aug 26 '20 at 14:20
  • The best solution is to change the code that creates such bad xml. – Alexander Petrov Aug 26 '20 at 14:21
  • @AlexanderPetrov It's not a bad xml. It's the nature of requirement. And generating XML dynamically like this is pretty common too. – Harshil Doshi Aug 26 '20 at 14:24
  • 1
    This is bad xml because dynamically created elements should have the same name and be inside a separate node. – Alexander Petrov Aug 26 '20 at 14:29
  • 1
    @HarshilDoshi Check the answer on this post (https://stackoverflow.com/questions/13171525/converting-xml-to-a-dynamic-c-sharp-object) by `Heather D`, not only is it short and simpler than the one I linked above, but it gives direction on using an `Expando Object` instead of a concrete class as well as how to get the property names. – Ryan Wilson Aug 26 '20 at 14:37

2 Answers2

0
public class Root
{
    // Mandatory elements
    public string Element1 { get; set; }
    public string Element2 { get; set; }
    public string Element3 { get; set; }

    // Dynamic elements
    public List<string> Elements { get; set; }
}

We can use linq2xml to parse xml and populate the class with data.

var xml = XElement.Load("test.xml");
Root root = new Root();

root.Element1 = xml.Element("Element1").Value;
root.Element2 = xml.Element("Element2").Value;
root.Element3 = xml.Element("Element3").Value;
xml.Element("Element1").Remove();
xml.Element("Element2").Remove();
xml.Element("Element3").Remove();

root.Elements = xml.Elements()
    .Where(elem => elem.Name.LocalName.Contains("Element"))
    .Select(elem => elem.Value)
    .ToList();

Console.WriteLine(root.Element1);
Console.WriteLine(root.Element2);
Console.WriteLine(root.Element3);
foreach (var text in root.Elements)
{
    Console.WriteLine(text);
}

The best solution is to change the code that creates such xml. For example, xml might look like this

<root>
  <!-- Mandatory elements -->
  <Element1>text1</Element1>
  <Element2>text2</Element2>
  <Element3>text3</Element3>

  <!-- Dynamic elements -->
  <Elements>
    <Element>textA</Element>
    <Element>textB</Element>
    <Element>textC</Element>
  </Elements>
</root>

It's much easier to work with. And you can easily use deserialization.

Ryan Wilson
  • 10,223
  • 2
  • 21
  • 40
Alexander Petrov
  • 13,457
  • 2
  • 20
  • 49
  • Doesn't meet the OP's requirements. "It's not a bad xml. It's the nature of requirement. And generating XML dynamically like this is pretty common too." – Ryan Wilson Aug 26 '20 at 14:58
  • @RyanWilson - The code I showed fully meets the author's requirements. It parses the author's xml correctly. – Alexander Petrov Aug 26 '20 at 15:05
  • The OP was very clear that the `xml` received may have any number of unknown nodes as well as dynamically formatted. This only meets the requirement if the OP has control over the construction of the `xml`, which from my interpretation of the comments and post, it isn't certain they do. I'm not saying that you're advice on the formatting of the `xml` is wrong, but this is not going to solve the problem if the OP doesn't have any say in how it's formatted. – Ryan Wilson Aug 26 '20 at 15:44
  • I removed the down vote. Please see my previous comment as to concerns over this answer. – Ryan Wilson Aug 26 '20 at 16:03
  • @RyanWilson - I understand you. In the first part of my message I gave an answer, in the second part - advice. – Alexander Petrov Aug 26 '20 at 16:06
  • Cool. To me it sounds like the OP could be receiving `xml` in other forms, not just what they gave in the example. If you check out that second post I linked to in the comments above, it gives a pretty slick solution which can handle any type of `xml` format and place into a `dynamic` object, to me it is by far the more preferred approach as it can accommodate anything you throw at it. – Ryan Wilson Aug 26 '20 at 16:09
0

You can create a list of generic anonymous elements. It's generic because maybe you need the data types before serialization/after deserialization.
Note that the only purpose of the AnonymousElement abstract class (which is not generic) is for letting to create the list in Root class without knowing the data types until you call AddAnonymousElement at run time.

If you don't need the generic behavior you can remove the abstract class and change the derived AnonymousElement class to a regular class.

[Serializable]
class Root
{
    public string TagNameElement1 { get; set; }
    public string TagNameElement2 { get; set; }
    public string TagNameElement3 { get; set; }

    public object ValueElement1 { get; set; }
    public object ValueElement2 { get; set; }
    public object ValueElement3 { get; set; }

    public List<AnonymousElement> AnonymousElements = new List<AnonymousElement>();
    public void AddAnonymousElement<T>(string tagname, T value)
    {
        AnonymousElement<T> elem = new AnonymousElement<T>(tagname, value);
        AnonymousElements.Add(elem);
    }
}

[Serializable]
public abstract class AnonymousElement
{
    public string TagName { get; set; }
    public AnonymousElement(string tagname)
    {
        this.TagName = tagname;
    }

}
public class AnonymousElement<T>: AnonymousElement
{
    public T Value { get; set; }

    public AnonymousElement(string _TagName,T _Value):base(_TagName)
    {
        this.TagName = _TagName;
        this.Value = _Value;
    }
}
Jonathan Applebaum
  • 5,738
  • 4
  • 33
  • 52