Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize a list with multiple data types in C# from xml?

I want to deserialize/serialize the following XML into an object.

    <Discounts>
      <Discount Type="Voucher" Key="ABCD00001" Percent="2" />
      <Discount Type="Quantity">
        <Periods>
          <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="1" />
          <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="2" />
        </Periods>
      </Discount>
    </Discounts>

Is it possible to have the @Type attribute define what type of object should be used to serialize?

For example, in C#:

[XmlArray]
[XmlArrayItem("Discount",typeof(Voucher)]
[XmlArrayItem("Discount",typeof(Quantity)]
public List<Discount> Discounts { get; set; }

I hope my explanation makes sense. Any help would be appreciated. Thanks.

Update after Andrew Anderson answer:

Here is the updated XML:

    <Discounts>
      <Discount xsi:Type="Voucher" Key="ABCD00001" Percent="2" />
      <Discount xsi:Type="Quantity">
        <Periods>
          <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="1" />
          <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="2" />
        </Periods>
      </Discount>
    </Discounts>

I changed the my classes to look like this:

    [Serializable]
    [XmlInclude(typeof(Voucher))]
    [XmlInclude(typeof(Quantity))]
    [XmlRoot("Discount")]
     public class Discount
    { ... }

    public class Quantity : Discount { }

    public class Voucher : Discount { }

When I deserialize this, the 'Discounts' list has two 'Discount' objects. I would expect at this point that the list should have a 'Quantity' object and 'Voucher' object. This could be because the my list is defined to have only a 'Discount' object. Following is the code for my 'Discounts' list object.

    [XmlArray]
    [XmlArrayItem("Discount")]
    public List<Discount> Discounts { get; set; }

The question now is how can I setup the list to contain the two different types of objects?

like image 614
chafnan Avatar asked Jun 14 '10 15:06

chafnan


1 Answers

If you have control over your XML you can use the XmlInclude attribute on your Discount base class to handle this.

For example (untested code ahead):

[Serializable]
[XmlInclude(typeof(Voucher))]
[XmlInclude(typeof(Quantity))]
[XmlRoot("Discount")]
public class Discount {    }

public class Quantity : Discount { }
public class Voucher : Discount { }

The resulting Xml will look like this:

<Discounts>
  <Discount xsi:type="Voucher" Key="ABCD00001" Percent="2" />
  <Discount xsi:type="Quantity">
    <Periods>
      <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="1" />
      <Period From="Thu, 31 Dec 2009 23:00:00 GMT" Quantity="2" />
    </Periods>
  </Discount>
</Discounts>

UPDATE:

Here is a sample set of classes and a console app to demonstrate serializing & deserialing from this format.

First the data definition:

[Serializable]
public class Shopping
{
    [XmlArray]
    [XmlArrayItem("Discount")]
    public List<Discount> Discounts { get; set; }
}

[Serializable]
[XmlInclude(typeof(Voucher))]
[XmlInclude(typeof(Quantity))]
[XmlRoot("Discount")]
public class Discount
{
    public int Amount { get; set; }
}

public class Quantity : Discount
{
    public int MyQuantity { get; set; }
}

public class Voucher : Discount
{
    public string MyVoucherName { get; set; }
}

And the test app:

public class Program
{
    static void Main(string[] args)
    {
        XmlSerializer xs = new XmlSerializer(typeof(Shopping));

        var myShopping = new Shopping();
        myShopping.Discounts = new List<Discount>();
        myShopping.Discounts.Add(new Voucher() {MyVoucherName = "Foo", Amount = 6});
        myShopping.Discounts.Add(new Quantity() { MyQuantity = 100, Amount = 6 });

        StringBuilder xml = new StringBuilder();
        XmlWriter xmlWriter = XmlWriter.Create(xml);

        xs.Serialize(xmlWriter, myShopping);

        Console.WriteLine("Serialized:");
        Console.WriteLine(xml);

        Console.WriteLine();
        Console.WriteLine("Deserialized:");

        TextReader tr = new StringReader(xml.ToString());
        var myNewShopping = (Shopping) xs.Deserialize(tr);

        if (myNewShopping.Discounts != null)
        {
            foreach (var discount in myNewShopping.Discounts)
            {
                if (discount is Voucher)
                {
                    var voucher = (Voucher) discount;
                    Console.WriteLine("Voucher - Amount={0}, Name={1}", voucher.Amount, voucher.MyVoucherName);
                }
                else if (discount is Quantity)
                {
                    var quantity = (Quantity)discount;
                    Console.WriteLine("Quantity - Amount={0}, #={1}", quantity.Amount, quantity.MyQuantity);
                }
                else
                {
                    Console.WriteLine("Discount - Amount={0}", discount.Amount);
                }
            }
        }
        else
        {
            Console.WriteLine("No Discounts found");
        }

        Console.ReadKey();
    }
like image 181
Andrew Anderson Avatar answered Sep 17 '22 12:09

Andrew Anderson