如何将不同类型的子元素反序列化为基类型的列表/集合,这是相应类的属性

Posted

技术标签:

【中文标题】如何将不同类型的子元素反序列化为基类型的列表/集合,这是相应类的属性【英文标题】:How to deserialize child elements of different types into list/collection of base type which is a property of the corresponding class 【发布时间】:2020-11-03 03:18:11 【问题描述】:

我有一个遗留系统,它使用一些以 XML 格式存储的数据。不能更改 XML 的格式而不引起严重问题(即读取该格式的工具和其他软件)。

XML 的结构如下:

<Things>
    <ThingOne Name="FirstThing">
        <ThingOne Name="FirstChildThing" />
        <ThingTwo Name="SecondChildThing"/>
    </ThingOne>
    <ThingTwo Name="SecondThing">
        <ThingOne Name="ThirdChildThing"  />
        <ThingTwo Name="FourthChildThing" />
    </ThingTwo>
</Things>

在我的场景中,“ThingOne”和“ThingTwo”是扩展“SomeThing”的类。 像这样:

public abstract class SomeThing 

    public int ItemNumber get; set;
    public string Name get; set;
    public List<SomeThing> ChildThings get; set;
    public abstract void InspectChildren();


public sealed class ThingOne : SomeThing

    public override void InspectChildren()
    
        /*Iterates Child Things and performs actions*/
    


public sealed class ThingTwo : SomeThing

    public override void InspectChildren()
    
        /*Iterates Child Things and performs actions different from thing one*/
    

而“Things”是一个包含“SomeThing”集合的类:

public sealed class Things

    /*Thing One and Thing Two should be deserialized to here*/
    public List<SomeThing> ThingItems get; set;

有一个东西检查员是这样的:

public static ThingInspector

    public static void InspectThings(IEnumerable<SomeThing> thingsToInspect)
    
        foreach(var thing in thingsToInspect)
        
            thing.InspectChildren()
        
    

但是,当我使用 XmlSerializer 将 XML 反序列化为“Things”实例时,它不会拾取“ThingOne”和“ThingTwo”并将它们添加到 ThingItems。

我尝试如下装饰类:

public sealed class Things:IList<SomeThing>

    /*Thing One and Thing Two should be deserialized to here*/
    [XmlArrayItem(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlArrayItem(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ThingItems get; set;

    /*Example doesn't include IList implementation, but it is present and correct*/



public abstract class SomeThing 

    [XmlIgnore]
    public int ItemNumber get; set;

    [XmlAttribute]
    public string Name get; set;

    [XmlArrayItem(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlArrayItem(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings get; set;
    
    public abstract void InspectChildren();

反序列化的对象是“Things”的一个实例,但 ThingItems 是空的。

我错过了什么?我已尝试更改 xml 格式,以便将“ThingOne”和“ThingTwo”包装在“ThingItems”列表中并且工作正常,但生产系统中的 xml 格式无法更改,而不会导致其他正在读取的软件出现故障,并且写入xml数据。

我的问题是我需要将“Things”的子元素反序列化为相应“Things”类的列表,而不是将对象结构强制到 XML 上。任何指导将不胜感激。

【问题讨论】:

无法做到。要创建您的列表,您需要一个基类和两个继承的类,这需要将类型属性添加到 xml 并使用 [XmlInclude]。由于您无法更改 xml,因此没有解决方案。如果没有 XmlInclude,您将拥有一个 c# 属性和两个 xml 标记名称。如果没有指明要使用哪种类型的方法,您就无法将一对二映射。 【参考方案1】:

您可以通过如下修改类型来反序列化您的 XML:

public interface IHasThings

    List<SomeThing> ChildThings  get; set; 


[XmlRoot("Things")]
public class Things : IHasThings

    [XmlElement(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlElement(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings  get; set;  = new List<SomeThing>();


public abstract class SomeThing : IHasThings

    [XmlIgnore]
    public int ItemNumber  get; set; 

    [XmlAttribute]
    public string Name get; set;

    [XmlElement(typeof(ThingOne), ElementName = "ThingOne")]
    [XmlElement(typeof(ThingTwo), ElementName = "ThingTwo")]
    public List<SomeThing> ChildThings get; set; = new List<SomeThing>();

    public abstract void InspectChildren();

注意事项:

通过将[XmlElementAttribute] 应用于您的列表,列表元素将被序列化为父元素的直接子元素,而无需插入额外的中间容器元素。

通过应用 [XmlElement(typeof(TThing), ElementName = "ThingName")] 来处理多态性,它指定了一个备用 XML 元素,即特定多态子类型的名称,类似于 XmlArrayItemAttribute(String, Type) 对具有容器元素的集合的工作方式。 XmlArrayItemXmlElement 不应应用于同一属性。

IHasThings 的引入是可选的,但可以更轻松地遍历您的层次结构,以统一的方式处理根容器和子事物。

ThingOneThingTwo 保持不变。

在 XML 反序列化中调试问题的一个好方法是手动构建您预期的反序列化图,然后对其进行序列化。观察到的和预期的 XML 之间的任何差异都可能表明您的反序列化错误所在的位置。使用您的数据模型,您会看到子集合的容器元素 &lt;ChildThings&gt; 的额外级别。

演示小提琴here.

【讨论】:

感谢您的帮助。在确定“XmlArrayItem”之前,我已经尝试过“XmlElement”属性,所以我将它们改回来并再次尝试,但我的测试仍然失败。因为我的测试仍然失败,你的实现和我的唯一区别是我在我的类上实现了“IList”(你的例子没有)我刚刚从类声明中删除了“IList”,它就开始工作了。 所以我想我的问题是我试图实现 IList。但这很好,因为我可以在构建解决方案时将数据类封装在列表适配器或其他东西中。关键是能够支持以前版本的数据存储,而这个解决方案很好地完成了这一点 @JD- - 确实你不能用XmlSerializer 序列化接口,见How to serialize an interface such as IList<T>。您只能序列化具体类。我没有意识到你也有这个问题。最后,如果您的问题得到解答,请mark it as such。

以上是关于如何将不同类型的子元素反序列化为基类型的列表/集合,这是相应类的属性的主要内容,如果未能解决你的问题,请参考以下文章

如何将不同名称的 XML 节点反序列化为相同的基本类型

将 Json 反序列化为实体框架无法将 int 转换为类型

将 json 反序列化为匿名类型列表

使用 jacksonObjectMapper 将 JSON 反序列化为不同类型

如何使用 WCF 将派生类型序列化为其基类型

C#将不同类型的xml数组反序列化为多个数组