简体   繁体   中英

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?

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();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM