The structure here is a bit convoluted so please bear with me. I hope that there's a way to do what a want to do, but if it's not possible then feel free to tell me!
Unfortunately I've not been involved in the design process of the XML file spec from the beginning, the goalposts have been moved a dozen times and the spec can't be amended for the sake of making it easier for me to do as any spec amendments are extortionately priced (yes, even renaming XML elements!)
Anyway I digress.
There are two different types of purchase order that have a slightly different file structure and slightly different processing logic and I am trying to handle them both with the same code instead of having two different projects for what is nearly identical.
Both types of purchase order derive from a set of abstract classes that determine basic business logic that both PO types share (quantity, cost, PO number, etc.).
Base classes
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;}
}
Derived classes
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
}
The base abstract class is then used in the code to process each purchase order and import them into our accounting system.
for (int i = pending.Transactions.Count -1; i > -1; i--) {
PO pendingOrder = (PO)pending.Transactions[i];
// Import PO type
}
The problem is that when I attempt to deserialize into each derived class as appropriate, it attempts to deserialize the Header
and LineItems
into the derived types PO_Head
and PO_Line
. Is there any way I can explicitly tell the XmlSerializer
to treat Header
and LineItems
as the derived class versions - CPO_Head and CPO_Line or POR_Head and POR_Line - depending on the class that is being serialized - CPO or POR respectively. Something akin to the below.
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;}
}
Kind of dug myself a hole with this one (first time using deserialization) so I'm hoping there's an easy way out that would save me having to rewrite a ton of the work I've done!
TIA
EDIT - code used to deserialize/serialize as requested
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);
}
EDIT - fixed
As per the marked correct answer I was able to resolve this by specifying the Type
in the XmlElement
and XmlArrayItem
attributes.
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>();
}
I think you are looking for the XmlArrayItemAttribute. According to the docs:
You can apply the XmlArrayItemAttribute to any public read/write member that returns an array, or provides access to one. For example, a field that returns an array of objects, a collection, an ArrayList, or any class that implements the IEnumerable interface.
The XmlArrayItemAttribute supports polymorphism--in other words, it allows the XmlSerializer to add derived objects to an array.
If I understand the example correctly, the attribute should be written with a list of the possible derived types that are allowed in the serialized enumerable, e.g.
[XmlArrayItem (typeof(PO_Line), ElementName = "PO_Line"),
XmlArrayItem (typeof(CPO_Line),ElementName = "CPO_Line")]
Since you are only passing a string, the attribute interprets that as the ElementName, not the type. The type must be passed using typeof(ClassName)
.