XmlSerializer:反序列化递归对象图

问题描述

鉴于我想反序列化以下 XML:

<?xml version="1.0" encoding="utf-8" ?>
<units>      
  <entity>
    <health max="1000"/>   
    <sprite texture="tank"/>
    <entity>        
      <sprite texture="tank-turret"/> <!-- this element is missing when i deserialize --!>
    </entity>    
  </entity>         
</units>

如何使用 XmlSerializer 反序列化这个递归对象图?

以下是我的最后一次尝试。它成功地反序列化了顶级对象(健康、精灵、实体),但它似乎没有在嵌套的实体节点中找到精灵元素。 我也尝试从 componentlist 派生实体,但它也不起作用。

public class UnitSerializer
{
    public abstract class item
    {
    }

    public class entity : item
    {
        [XmlArray("entity")]
        [XmlArrayItem(typeof(health))]
        [XmlArrayItem(typeof(entity))]
        [XmlArrayItem(typeof(sprite))]
        public componentlist entity2 { get; set; }
    }

    public abstract class component : item
    {
    }

    public class health : component
    {
        [XmlAttribute]
        public int max { get; set; }
    }

    public class sprite : component
    {
        [XmlAttribute]
        public string texture { get; set; }

    }

    public class componentlist : List<item>
    {
    }

    [XmlRoot("units")]
    public class units
    {
        [XmlArray("entity")]
        [XmlArrayItem(typeof(health))]
        [XmlArrayItem(typeof(entity))]
        [XmlArrayItem(typeof(sprite))]
        public componentlist entity { get; set; }
    }

    public void Read()
    {
        var x = new XmlSerializer(typeof(units),new[] {
                        typeof(componentlist),typeof(entity),typeof(health),typeof(sprite)
                });
        var fs = new FileStream("units.xml",FileMode.Open);
        XmlReader reader = new XmlTextReader(fs);
        var units = (units)x.Deserialize(reader);
    }
}

解决方法

您的类可以通过将 [XmlArray][XmlArrayItem] 的使用替换为 [XmlElement(typeof(TDerived))] 来修复:

public class UnitSerializer
{
    public abstract class item
    {
    }

    public class entity : item
    {
        [XmlElement("health",typeof(health))]
        [XmlElement("entity",typeof(entity))]
        [XmlElement("sprite",typeof(sprite))]
        public List<item> EntityList { get; set; }
    }

    public abstract class component : item
    {
    }

    public class health : component
    {
        [XmlAttribute]
        public int max { get; set; }
    }

    public class sprite : component
    {
        [XmlAttribute]
        public string texture { get; set; }
    }

    [XmlRoot("units")]
    public class units
    {
        [XmlElement("health",typeof(sprite))]
        public List<item> EntityList { get; set; }
    }

    public units Read(string filename)
    {
        var x = new XmlSerializer(typeof(units));
        using (var fs = new FileStream(filename,FileMode.Open))
        using (var reader = XmlReader.Create(fs))
        {
            return (units)x.Deserialize(reader);
        }
    }
}

注意事项:

  • [XmlArray] 表示应该使用包含元素序列的外部包装元素序列化集合,而 [XmlElement] 表示应该将集合序列化为没有包装的序列。您的 XML 示例使用没有包装元素的重复元素,因此应使用 [XmlElement]。它有点工作,因为您的 XML 是递​​归的——但在其他所有级别,重复元素都被错误地反序列化为包装器。这就解释了为什么在反序列化过程中会丢失部分而非全部数据。

  • 在您的 XML 示例中,多态元素由元素名称标识。 XmlSerializer(Type,Type[]) 构造函数应用于指定要使用 xsi:type 机制序列化的多态包含类型。由于 xsi:type 属性未出现在您的 XML 中,因此无需使用此构造函数。

    (此外,在使用 XmlSerializer 构造函数构造 XmlSerializer(Type,Type[]) 时,您必须静态缓存序列化程序以避免严重的内存泄漏。参见 Memory Leak using StreamReader and XmlSerializer为什么。)

  • XmlTextReader 自 .Net 2.0 起已弃用。改用 XmlReader.Create()

  • 应该处理 FileStreamXmlReader,最好通过 using 语句进行处理。

  • 我去掉了 public class componentlist : List<item>,只用一个 List<item> 代替了它。这主要是一个品味问题,但它确实可以更轻松地使用 Linq 的 .ToList() 设置此类列表的值。

演示小提琴here