如何将不同类型的子元素反序列化为基本类型的 list/collection,即相应 class 的 属性

How to deserialize child elements of different types into list/collection of base type which is a property of the corresponding class

我有一个遗留系统,它使用一些以 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”是 class扩展“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”是一个 class,包含“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。

我试过如下装饰 classes:

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”的列表中class,而不是将对象结构强制到XML。任何指导将不胜感激。

您可以通过如下修改类型来反序列化您的 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")] which specifies an alternate XML element, name for that specific polymorphic child type, similarly to how XmlArrayItemAttribute(String, Type) 处理,适用于具有容器元素的集合。 XmlArrayItemXmlElement 不应应用于相同的 属性。

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

  • ThingOneThingTwo不变。

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

演示 fiddle here.