如何使用XmlSerializer将异构子节点反序列化为集合?

时间:2013-11-11 01:50:24

标签: c# .net xml xmlserializer

我正在使用C#/ .NET反序列化类似于此的XML文件:

<?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>

对于反序列化的XML,我的类看起来像:

[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; }
} 

反序列化时,我希望将Book元素的所有子节点(&lt; Thing1&gt;到&lt; ThingN&gt;)反序列化为Book的Things集合。但是,我无法弄清楚如何实现这一目标。现在,我无法将Thing节点存储在ThingElements集合中(通过XmlAnyElement)。

有没有办法将异构子节点反序列化为一个集合(非XmlElements)?

2 个答案:

答案 0 :(得分:2)

如果您想将其序列化为一组简单的KeyValuePairs,则可以使用自定义Struct来完成此操作。不幸的是,内置的通用KeyValuePair不起作用。

但是,鉴于以下类定义:

[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;
    }
}

当我使用这些信息序列化一个对象时,我得到一个看起来像这样的XML结构。

<?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>

我也能够成功地将XML读回到对象中并打印出信息。

您可以在控制台应用程序中自行测试,以查看结果。

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);
        }
    }
}

答案 1 :(得分:1)

我不确定我是否建议这样做,但确实有效......

XmlSerializer有一个“UnknownElement”事件,将为你的所有Thing1 ... ThingN元素调用它。您可以像这样处理此事件并反序列化您的事物:

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);
    }
  }
};

您显然需要更改“Thing”测试以适合您的实际数据。例如,您可能希望尝试将Book下的所有元素反序列化为“Thing”,在这种情况下,您将删除StartsWith条件。

您还需要使用XmlTextAttribute

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

  [XmlTextAttribute]
  public string Value { get; set; }
}