【问题标题】:How can I deserialize heterogeneous child nodes into a collection, using XmlSerializer?如何使用 XmlSerializer 将异构子节点反序列化为集合?
【发布时间】:2013-11-11 01:50:23
【问题描述】:

我正在使用 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 元素的所有子节点()反序列化为 Book 的 Things 集合。但是,我无法弄清楚如何做到这一点。现在,我将 Thing 节点存储在 ThingElements 集合中(通过 XmlAnyElement)。

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

【问题讨论】:

  • 您一直在说异构子节点,但您的示例中只有一种类型,即 Thing 类型。还有更多吗?它们是从公共基类或接口继承的吗?它们是 Duck Typable 吗?
  • 意图是 Book 类的所有子节点都应该反序列化为 Thing 对象,而不考虑元素名称。因此,如果子节点是 Thing1、Thing2、Foo3 和 Bar4,则在反序列化时它们都是 Thing 对象。此外,每个子节点的 InnerText 将转到 Value 属性。
  • 这是一种您无法控制的 XML 格式吗?否则,我建议您将它们简单地序列化为实际的 KeyValuePairs。即使是List&lt;KeyValuePair&lt;Tkey, TValue&gt;&gt; 似乎也是一个不错的选择。
  • 如果我有一个 List> 类型的属性,我会为该属性应用什么属性以进行反序列化?还是您建议我手动完成?
  • 你可以在下面看到我的解决方案。只要您可以控制格式,那么我认为它会满足您的需求。

标签: c# .net xml xmlserializer


【解决方案1】:

如果您想将其序列化为一组简单的 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);
        }
    }
}

【讨论】:

    【解决方案2】:

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

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

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

    您还需要使用 XmlTextAttribute 标记 Value

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

    【讨论】:

    • 感谢分享这个替代方案;我不知道 UnknownElement 事件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    • 2014-03-20
    相关资源
    最近更新 更多