0

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?

Thomas
  • 1,563
  • 3
  • 17
  • 37
  • Your problem is very similar to the problem from [serializing a list of KeyValuePair to XML](https://stackoverflow.com/a/30443169/3744182) or [Xml Sequence deserialization with RestSharp](https://stackoverflow.com/a/32885108/3744182). Both have answers showing two different ways to deserialize a sequence of paired elements using `XmlSerializer`. Are those answers sufficient, or do you need more help? – dbc Dec 04 '17 at 02:57
  • Do you only need to deserialize, or also re-serialize in the same format? – dbc Dec 04 '17 at 18:20
  • See also [Serialize parallel arrays to XML in C# in a specific order](https://stackoverflow.com/q/47641622/3744182) which does serialization as well as deserialization. – dbc Dec 06 '17 at 22:20

1 Answers1

1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340