5

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?

6
  • Also, are you flexible on the look of the xml, or does it have to be exactly as you've posted? Commented Jun 14, 2010 at 15:55
  • Are you in charge of the format? If so, why not replace "Discount" with "VoucherDiscount" and "QuantityDiscount" in the XML? Commented Jun 14, 2010 at 15:59
  • It would seem the easiest to use a stylesheet on it when it comes in and when it goes out, but I would like to avoid changing anything. Commented Jun 14, 2010 at 16:51
  • Okay - so I see the update... thanks. I am assuming that you can loop through your Discounts list and do something like "if (myDiscount is Voucher) { Voucher v = (Voucher) myDiscount; }" .... and so on. Commented Jun 14, 2010 at 17:39
  • Not quite. I gave the Voucher another property and that new property is not loaded at all during deserialization. Also, when I serialize the again the 'xsi:Type' is gone. Commented Jun 14, 2010 at 17:52

1 Answer 1

6

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();
    }
Sign up to request clarification or add additional context in comments.

4 Comments

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.
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?
Sorry, I deleted my first comment, because I read your answer wrong and applied the XmlInclude in the wrong place.
Can you update your post with what you're seeing now? I'm not clear on what is still wrong from your description.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.