views:

76

answers:

1

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?

+1  A: 

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();
    }
Andrew Anderson
I do have it on a class in my sample. In your post you decorate the List of Discounts - aside from [XmlArray] you shouldn't need any other attributes.
Andrew Anderson
That worked. The question now is, the typeof object that is in the List now is "Discount" as I would like it to be the type of object that is loaded such as "Quantity" or "Voucher". Anyway of getting the loaded objects to be correct in the list?
chafnan
Sorry, I deleted my first comment, because I read your answer wrong and applied the XmlInclude in the wrong place.
chafnan
Can you update your post with what you're seeing now? I'm not clear on what is still wrong from your description.
Andrew Anderson