How can I deserialize heterogeneous child nodes in a collection using XmlSerializer?

I am using C # /. NET to deserialize an XML file that looks similar 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 deserialized XML look like this:

 [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 child nodes of the Book element (<Thing1> via <ThingN>) to be deserialized into the Book Things collection. However, I cannot figure out how to do this. Right now I am delaying the storage of Thing nodes in the ThingElements collection (via XmlAnyElement).

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

+6
source share
2 answers

If you want to serialize this as a set of simple KeyValuePairs, you can use custom Struct for this. Unfortunately, the built-in KeyValuePair generator will not 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 can also successfully read this XML back into the object and print the information.

You can check this for yourself in the 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); } } } 
+2
source

I'm not sure I recommend this, but it works ...

XmlSerializer has an UnknownElement event that will be raised for all of your Thing1 ... ThingN elements. You can handle this event and deserialize your stuff like this:

 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 will obviously need to modify the β€œThing” test according to your actual data. For example, you can try and deserialize all the elements in a book as a β€œThing”, in which case you must remove the StartsWith condition.

You will also need to mark the value with XmlTextAttribute

 public class Thing { public string Name { get; set; } [XmlTextAttribute] public string Value { get; set; } } 
+1
source

Source: https://habr.com/ru/post/957802/


All Articles