Doug Ramirez
Doug Ramirez

Reputation: 41

How to deserialize an XML document into a List<> while overriding the item names

I am trying to deserialize an XML document into a List<>. My goal is the have the List<> have a public static method that returns itself populated from the XML document.

Here is the XML:

<?xml version="1.0"?>
<root>
    <item>
        <name>Blue</name>
    </item>
    <item>
        <name>Red</name>
    </item>
</root>

Here is the class for the item (BillingItem.cs):

using System;
using System.Xml.Serialization;

namespace DeserializeExample
{
    [Serializable]
    [XmlRoot("item")]
    public class BillingItem
    {
        [XmlElement("name")]
        public string Description { get; set; }
    }
}

Here is the class that is the list (BillingItems.cs):

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace DeserializeExample
{
    [Serializable]
    [XmlRoot("root")]
    public class BillingItems : List<BillingItem>
    {
        public static BillingItems GetBillingItems()
        {
            using (TextReader reader = new StreamReader("BillingItems.xml"))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(BillingItems));
                return (BillingItems)serializer.Deserialize(reader);
            }
        }
    }
}

And here is the console app with an example of how I would like to be able to get the list (Program.cs):

namespace DeserializeExample
{
    class Program
    {
        static void Main(string[] args)
        {
            BillingItems billingItems = BillingItems.GetBillingItems();
        }
    }
}

When the console app runs, the list is empty. I think it's related to how I am using the attributes, but I am unable to determine how to make this work.

I hope the example is simple, explained well, and that someone can help.

Upvotes: 3

Views: 2402

Answers (3)

Doug Ramirez
Doug Ramirez

Reputation: 41

Ok, while I don't like this because I believe there is something in the XmlSerializer that will do this for me, ug, but in the meantime I am going to do the following.

Adding an encapsulating class with the list I really want:

using System.Xml.Serialization;

namespace DeserializeExample
{
    [XmlRoot("root")]
    public class BillingItemResult
    {
        [XmlElement("item")]
        public BillingItems Items { get; set; }
    }
}

Then in my BillItems.cs class the private static void method will get the encapsulating object and return it's list of billing items:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace DeserializeExample
{
    [Serializable]
    [XmlRoot("root")]
    public class BillingItems : List<BillingItem>
    {
        public static BillingItems GetBillingItems()
        {
            using (TextReader reader = new StreamReader("BillingItems.xml"))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(BillingItemResult));
                BillingItemResult billingItemResult = (BillingItemResult)serializer.Deserialize(reader);
                return billingItemResult.Items;
            }
        }
    }
}

This allows the design and convention of the existing application to remain intact. Here is a sample from the CLI:

using System.Collections.Generic;
namespace DeserializeExample
{
    class Program
    {
        static void Main(string[] args)
        {
            BillingItems billingItems = BillingItems.GetBillingItems();
        }
    }
}

While this works, I still believe there is something more simple that doesn't require the additional wrapping class.

Upvotes: 0

Kevin D.
Kevin D.

Reputation: 911

From your example it looks like serialization may not be necessary. If it isn't, consider parsing using LINQ-to-XML:

using System.Xml.Linq;

public class BillingItems : List<BillingItem>
{
    // Constructor.
    private BillingItems(List<BillingItem> items) : base(items) { }

    public static BillingItems GetBillingItems()
    {
        var doc = XDocument.Load("BillingItems.xml");
        var items = (from i in doc.Element("root").Elements("item")
                        select new BillingItem { Description = i.Element("name").Value })
                    .ToList();
        return new BillingItems(items);
    }
}

You may also want to consider using an IEnumerable<> rather than a List<> unless you plan on inserting/removing items.

EDIT: Modified the above code so that BillingItems inherits a List<>, per the OP's comment below.

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1063298

I would encapsulate the list - in part for simplicity (avoiding some gnarly overloads the the XmlSerializer constructor), and in part because there are some glitches here on CF etc that make it unreliable. But:

public class BillingItem
{
    [XmlElement("name")]
    public string Description { get; set; }
}
[XmlRoot("root")]
public class MyResult
{
    [XmlElement("item")]
    public List<BillingItem> Items { get; set; }
}

(I have removed all unnecessary other attributes)

You then just return a MyResult (named more appropriately, obviously), and it will serialize as per your example.

Upvotes: 3

Related Questions