XmlSerializer:反序列化递归对象图

Posted

技术标签:

【中文标题】XmlSerializer:反序列化递归对象图【英文标题】:XmlSerializer: deserialize recursive object graph 【发布时间】:2021-06-24 11:57:06 【问题描述】:

鉴于我想反序列化以下 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 反序列化这个递归对象图?

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

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

【问题讨论】:

【参考方案1】:

您的课程可以通过将 [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(health))]
        [XmlElement("entity", typeof(entity))]
        [XmlElement("sprite", 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(Type, Type[]) 构造函数构造XmlSerializer 时,必须静态缓存序列化程序以避免严重的内存泄漏。请参阅Memory Leak using StreamReader and XmlSerializer 了解原因。)

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

应该处理FileStreamXmlReader,最好是通过using 语句。

我删除了 public class componentlist : List&lt;item&gt; 并用 List&lt;item&gt; 替换它。这主要是一个口味问题,但它确实可以更轻松地使用 Linq 的 .ToList() 设置此类列表的值。

演示小提琴here.

【讨论】:

感谢您的出色而令人筋疲力尽的回答!我现在也可以通过从 entity 初始化单元来摆脱重复的 EntityList 属性定义。

以上是关于XmlSerializer:反序列化递归对象图的主要内容,如果未能解决你的问题,请参考以下文章

C# XML序列化和反序列化(XmlSerializer)

使用 XmlSerializer 将空 xml 属性值反序列化为可为空的 int 属性

XMLSerializer 不反序列化 XML

无法使用 c# xmlserializer 反序列化以前序列化的 XML

使用 XmlSerializer.Deserialize 反序列化时何时调用类构造函数?

XmlSerializer c++ 使用多个命名空间反序列化