Question

I'm using C#/.NET to deserialize a XML file that looks akin to this:

<?xml version="1.0" encoding="utf-8" ?>
<Books>
  <Book
    Title="Animal Farm"
    >
    <Thing1>""</Thing1>
    <Thing2>""</Thing2>
    <Thing3>""</Thing3>
    ...
    <ThingN>""</ThingN>
  </Book>
  ... More Book nodes ...
</Books>

My classes, for the deserialized XML, look like:

[XmlRoot("Books")]
public class BookList
{
    // Other code removed for compactness. 

    [XmlElement("Book")]
    public List<Book> Books { get; set; }
}

public class Book
{
    // Other code removed for compactness. 

    [XmlAttribute("Title")]
    public string Title { get; set; }

    [XmlAnyElement()]
    public List<XmlElement> ThingElements { get; set; }

    public List<Thing> Things { get; set; }
}  

public class Thing
{
    public string Name { get; set; }
    public string Value { get; set; }
} 

When deserializing, I want all the child nodes of the Book element (<Thing1> through <ThingN>) to be deserialized into a Book's Things collection. However, I'm unable to figure out how to accomplish that. Right now, I'm stuck storing the Thing nodes in the ThingElements collection (via XmlAnyElement).

Is there a way to deserialize heterogeneous child nodes into a collection (of non-XmlElements)?

Was it helpful?

Solution

If you wanted to serialize this as a set of simple KeyValuePairs you could use a custom Struct to accomplish this. Unfortunately the built in generic KeyValuePair won't work.

But, given the following class definitions:

[XmlRoot("Books")]
public class BookList
{
    [XmlElement("Book")]
    public List<Book> Books { get; set; }
}

public class Book
{
    [XmlAttribute("Title")]
    public string Title { get; set; }

    [XmlElement("Attribute")]
    public List<AttributePair<String, String>> Attributes { get; set; }
}

[Serializable]
[XmlType(TypeName = "Attribute")]
public struct AttributePair<K, V>
{
    public K Key { get; set; }
    public V Value { get; set; }

    public AttributePair(K key, V val)
        : this()
    {
        Key = key;
        Value = val;
    }
}

When I serialize an object using this information I get an XML structure that looks something like this.

<?xml version="1.0"?>
<Books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Book Title="To win a woman">
    <Attribute>
      <Key>Author</Key>
      <Value>Bob</Value>
    </Attribute>
    <Attribute>
      <Key>Publish Date</Key>
      <Value>1934</Value>
    </Attribute>
    <Attribute>
      <Key>Genre</Key>
      <Value>Romance</Value>
    </Attribute>
  </Book>
</Books>

I am also able to successfully read that XML right back into an object and print out the information.

You can test it out for yourself in a console application to see the results.

using(var file = File.OpenRead("booklist.xml"))
{
    var readBookCollection = (BookList)serializer.Deserialize(file);

    foreach (var book in readBookCollection.Books)
    {
        Console.WriteLine("Title: {0}", book.Title);

        foreach (var attributePair in book.Attributes)
        {
            Console.CursorLeft = 3;
            Console.WriteLine("Key: {0}, Value: {1}", 
                attributePair.Key, 
                attributePair.Value);
        }
    }
}

OTHER TIPS

I'm not sure I'd recommend doing this but it does work...

XmlSerializer has an "UnknownElement" event which will be called for all of your Thing1...ThingN elements. You can handle this event and deserialize your Things like so:

serializer.UnknownElement += (obj, eargs) =>
{
  var element = eargs.Element;
  var book = eargs.ObjectBeingDeserialized as Book;

  //Are we deserializing a book and do we have an unrecognized Thing element?
  if (book != null && element.Name.StartsWith("Thing"))
  {            
    //Deserialize our thing
    using (var stringReader = new StringReader(element.OuterXml))
    {
      var thingSerializer = new XmlSerializer(typeof(Thing), new XmlRootAttribute(element.Name));
      var thing = (Thing)thingSerializer.Deserialize(stringReader);

      //Name can't be mapped for us, assign this manually
      thing.Name = element.Name;

      book.Things.Add(thing);
    }
  }
};

You'd obviously need to change the "Thing" test to suit your real data. For example you might want to try and deserialize all elements under Book as a "Thing" in which case you'd remove the StartsWith condition.

You'll also need to mark Value with the XmlTextAttribute

public class Thing
{
  public string Name { get; set; }

  [XmlTextAttribute]
  public string Value { get; set; }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top