将类属性反序列化为派生类,而不是基础抽象类

时间:2018-11-12 17:05:54

标签: c# xml-deserialization

这里的结构有点复杂,请耐心等待。我希望有一种方法可以做想做的事情,但是如果不可能的话,请随时告诉我!

不幸的是,我从一开始就没有参与XML文件规范的设计过程,目标杆已经移动了十几次,并且为了使我更容易做而无法修改该规范。因为任何规范修订都定价过高(是的,甚至重命名XML元素!)

无论如何,我离题了。

有两种不同类型的采购订单,它们的文件结构略有不同,处理逻辑也略有不同,我正在尝试使用相同的代码来处理它们,而不是针对几乎相同的项目使用两个不同的项目。

两种类型的采购订单都来自一组抽象类,这些抽象类确定两种PO类型共享的基本业务逻辑(数量,成本,PO编号等)。

基类

abstract class PO {
  [XmlIgnore]
  abstract POType PurchaseOrderType {get;}
  [XmlIgnore]
  abstract PO_Head Header {get;set;}
  [XmlIgnore]
  abstract List<PO_Line> LineItems {get;set;}
}

abstract class PO_Head {
  [XmlIgnore]
  abstract string PONumber {get;set;}
}

abstract class PO_Line {
  [XmlIgnore]
  abstract string ItemCode {get;set;}
  [XmlIgnore]
  abstract decimal UnitCost {get;set;}
  [XmlIgnore]
  abstract int OrderQty {get;set;}
}

派生类

class POR : PO {
  // POR implementations
}

class POR_Head : PO_Line {
  // POR implementations
}

class POR_Line : PO_Line {
  // POR implementations
}

class CPO : PO {
  // CPO implementations
}

class CPO_Head : PO_Line {
  // CPO implementations
}

class CPO_Line : PO_Line {
  // CPO implementations
}

然后,代码中使用基本抽象类处理每个采购订单并将其导入我们的会计系统。

for (int i = pending.Transactions.Count -1; i > -1; i--) {
  PO pendingOrder = (PO)pending.Transactions[i];
  // Import PO type
}

问题是,当我尝试对每个派生类进行反序列化时,它会尝试将HeaderLineItems反序列化为派生类型PO_HeadPO_Line 。我有什么办法可以明确地告诉XmlSerializerHeaderLineItems当作派生类版本-CPO_Head和CPO_Line或POR_Head和POR_Line-取决于要序列化的类-分别为CPO或POR。类似于以下内容。

class CPO : PO {
  [XmlIgnore]
  override POType PurchaseOrderType => POType.CPO;

  [XmlElement("CPO_Head")]
  [XmlDeserializerType(typeof(CPO_Head))]
  override PO_Head Header {get;set;}

  [XmlArray("CPO_Lines")]
  [XmlArrayItem("CPO_Line")]
  [XmlDeserializerType(typeof(CPO_Line))]
  override List<PO_Line> LineItems {get;set;}
}

有点想用这个去挖个洞(第一次使用反序列化),所以我希望有一个简单的方法可以省掉我重写的大量工作!

TIA

编辑-用于根据要求反序列化/序列化的代码

public static void SerializeToXmlFile(object obj, FileInfo dstFile)
{
    XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
    using (FileStream fs = dstFile.Open(FileMode.Create, FileAccess.Write))
    {
        xmlSerializer.Serialize(fs, obj);
    }
}


public static object DeserializeFromXmlFile(FileInfo srcFile, Type type)
{
    XmlSerializer xmlSerializer = new XmlSerializer(type);
    using (FileStream fs = srcFile.Open(FileMode.Open, FileAccess.Read))
    {
        return xmlSerializer.Deserialize(fs);
    }
}

public static void Main(string[] args) 
{
    // Deserialize from XML file
    FileInfo srcFile = new FileInfo("path\to\file");
    Type t;
    if (srcFile.Name.Substring(0,3) == "CPO")
        t = typeof(CPO);
    else if (srcFile.Name.Substring(0,3) == "POR")
        t = typeof(POR);

    PO po = DeserializeFromXmlFile(file, t);

    // Process the file
    // ...

    // Serialize back to file
    FileInfo dstFile = new FileInfo("new\path\to\file");
    SerializeToXmlFile(po, dstFile);
}

编辑-固定

根据标记为正确的答案,我能够通过在TypeXmlElement属性中指定XmlArrayItem来解决此问题。

class POR {
  [XmlElement(typeof(POR_Head), ElementName="PO_Head")]
  override PO_Head Header {get;set;} = new POR_Head();
  [XmlArray("PO_Lines")]
  [XmlArrayItem(typeof(POR_Line), ElementName="PO_Line")]
  override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}

class CPO {
  [XmlElement(typeof(CPO_Head), ElementName="PO_Head")]
  override PO_Head Header {get;set;} = new CPO_Head();
  [XmlArray("PO_Lines")]
  [XmlArrayItem(typeof(CPO_Line), ElementName="PO_Line")]
  override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}

1 个答案:

答案 0 :(得分:1)

我认为您正在寻找XmlArrayItemAttribute。根据文档:

  

您可以将XmlArrayItemAttribute应用于返回数组或提供对数组的访问的任何公共读/写成员。例如,一个返回对象数组,集合,ArrayList或实现IEnumerable接口的任何类的字段。

     

XmlArrayItemAttribute支持多态性-换句话说,它允许XmlSerializer将派生对象添加到数组中。

如果我正确理解了该示例,则应在属性中写出序列化可枚举中允许的可能派生类型的列表,例如

[XmlArrayItem (typeof(PO_Line), ElementName = "PO_Line"),
 XmlArrayItem (typeof(CPO_Line),ElementName = "CPO_Line")]

由于仅传递字符串,因此属性会将其解释为ElementName而不是类型。必须使用typeof(ClassName)传递类型。