I have the following XML from which I need to map the "DUE" and "RATE" to a list of objects with XmlSerializer. There can be zero to many, and they're always coming as a pair with the same "idx".
<INVOICE ID="4">
<STATUS>S</STATUS>
<TOTAL>6230.00</TOTAL>
<DUE idx="1">14.12.17</DUE>
<RATE idx="1">6230.00</RATE>
</INVOICE >
<INVOICE ID="5">
<STATUS>S</STATUS>
<TOTAL>3270.00</TOTAL>
<DUE idx="1">30.11.17</DUE>
<RATE idx="1">1090.00</RATE>
<DUE idx="2">07.12.17</DUE>
<RATE idx="2">1090.00</RATE>
<DUE idx="3">14.12.17</DUE>
<RATE idx="3">1090.00</RATE>
</INVOICE>
I have the following setup which is working fine without a list of "Rate" and "Due":
[Serializable]
public class UserInvoicesDto
{
[XmlElement("INVOICE")]
public List<UserInvoiceDto> Invoices { get; set; }
}
[Serializable, XmlRoot("INVOICE")]
public class UserInvoiceDto
{
[XmlAttribute("id")]
public int InvoiceId { get; set; }
[XmlElement("TOTAL")]
public string Total { get; set; }
}
And then I have the following class.
[Serializable]
public class InvoicesDueDates
{
[XmlAttribute("idx")]
public string Id { get; set; }
[XmlElement("DUE")]
public string DueDate { get; set; }
[XmlElement("RATE")]
public string Rate { get; set; }
}
Is it somehow possible?
If you only need to deserialize, you can use do so using XmlSerializer
to the following types:
[XmlRoot(ElementName = "DUE")]
public class DueDTO
{
[XmlAttribute(AttributeName = "idx")]
public string Idx { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "RATE")]
public class RateDTO
{
[XmlAttribute(AttributeName = "idx")]
public string Idx { get; set; }
[XmlText]
public decimal Text { get; set; }
}
[XmlRoot(ElementName = "INVOICE")]
public partial class InvoicesDTO
{
[XmlAttribute(AttributeName = "ID")]
public string Id { get; set; }
[XmlElement(ElementName = "STATUS")]
public string Status { get; set; }
[XmlElement(ElementName = "TOTAL")]
public decimal Total { get; set; }
[XmlElement(ElementName = "DUE")]
public List<DueDTO> Due { get; set; }
[XmlElement(ElementName = "RATE")]
public List<RateDTO> Rate { get; set; }
}
Then, to combine the Rate
and Due
list into a single InvoicesDueDates
collection, you can use LINQ, e.g. as follows:
public partial class InvoicesDTO
{
public InvoicesDueDates[] InvoicesDueDates
{
get
{
// To make suure we handle cases where only a Rate or Due item of a specific index is present,
// perform left outer joins with all indices on both Rate and Due.
// https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-left-outer-joins
var query = from i in Due.Select(d => d.Idx).Concat(Rate.Select(r => r.Idx)).Distinct()
join due in Due on i equals due.Idx into dueGroup
// Throw an exception if we have more than one due item for a given index
let due = dueGroup.SingleOrDefault()
join rate in Rate on i equals rate.Idx into rateGroup
// Throw an exception if we have more than one rate item for a given index
let rate = rateGroup.SingleOrDefault()
select new InvoicesDueDates { Id = i, DueDate = due == null ? null : due.Text, Rate = rate == null ? (decimal?)null : rate.Text };
return query.ToArray();
}
}
}
public class InvoicesDueDates
{
public string Id { get; set; }
public string DueDate { get; set; }
public decimal? Rate { get; set; }
}
Notes:
This solution takes advantage of the fact that, when XmlSerializer
is deserializing a List<T>
property and encounters list elements interleaved with other elements, it will append each list element encountered to the growing list.
If you re-serialize an InvoicesDTO
the result will look like:
<INVOICE ID="5">
<STATUS>S</STATUS>
<TOTAL>3270.00</TOTAL>
<DUE idx="1">30.11.17</DUE>
<DUE idx="2">07.12.17</DUE>
<DUE idx="3">14.12.17</DUE>
<RATE idx="1">1090.00</RATE>
<RATE idx="2">1090.00</RATE>
<RATE idx="3">1090.00</RATE>
</INVOICE>
Notice that all the information has been retained and re-serialized but the <RATE>
and <DUE>
sequences have been separated out.
If you need to re-serialize with interleaved <RATE>
and <DUE>
elements, you will have to adopt a different strategy, such as the ones from serializing a list of KeyValuePair to XML or Xml Sequence deserialization with RestSharp.
I auto-generated the DTO classes using https://xmltocsharp.azurewebsites.net/ then modified them to fit my naming contentions.
Sample working .Net fiddle.