Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing a List<> exported as an ICollection<> to XML

I have a C# .NET 3.5 application where I would like to serialize a class containing a List<> to XML. My class looks like this:

[XmlRoot("Foo")]
class Foo
{
    private List<Bar> bar_ = new List<Bar>();

    private string something_ = "My String";

    [XmlElement("Something")]
    public string Something { get { return something_; } }

    [XmlElement("Bar")]
    public ICollection<Bar> Bars
    {
        get { return bar_; }
    }
}

If I populate it like this:

Bar b1 = new Bar();
// populate b1 with interesting data
Bar b2 = new Bar();
// populate b2 with interesting data

Foo f = new Foo();
f.Bars.Add(b1);
f.Bars.Add(b2);

And then serialize it like this:

using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(@"C:\foo.xml"))
{
    System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo));
    serializer.Serialize(textWriter, f);
}

I get a file that looks like this:

<Foo>
    <Something>My String</Something>
</Foo>

But, what I want is XML that looks like this:

<Foo>
    <Something>My String</Something>
    <Bar>
        <!-- Data from first Bar -->
    </Bar>
    <Bar>
        <!-- Data from second Bar -->
    </Bar>
</Foo>

What do I need to do to get the List<> to appear in the XML?

like image 816
PaulH Avatar asked Sep 20 '11 18:09

PaulH


2 Answers

The XmlSerializer requires that serializable properties have a setter. Besides that, the XmlSerializer can not serialize interface properties. The following code will work:

[XmlElement("Bar")]
public List<Bar> Bars
{
    get { return bar_; }
    set { throw new NotSupportedException("This property 'Bars' cannot be set. This property is readonly."); }
}

If you don't like this solution (the exception is kinda ugly) then you could implement IXmlSerializable and write your own custom serialization.

Edit: Artur Mustafin is right, members that implement IEnumerable or ICollection don't need a setter, as is explained on this msdn page:

The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item indexed property (indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter to the Add method must be the same type as is returned from the Item property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexed Item property, not by calling GetEnumerator.

like image 127
Elian Ebbing Avatar answered Sep 30 '22 16:09

Elian Ebbing


Giving a correct answer, there is no point to create ugly setter to the List<T> public property, to throw an exception.

This is because List<> is already implements ICollection<T> and provides method with signature void Add(T object) which is used by Serialization mechanism;

You are only have to add the setter to the public properties being serialized, and change ICollection<T> to List<T>:

[XmlRoot("Foo")]
public class Foo
{
    private List<Bar> bar_ = new List<Bar>();

    [XmlElement("Something")]
    public string Something { get; set; }

    [XmlElement("Bar")]
    public List<Bar> Bars { get { return bar_; } }
}

You will get an output:

<?xml version="1.0" encoding="utf-8"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Something>My String</Something>
  <Bar />
  <Bar />
</Foo>

Also, it is a better idea to serialize xml in-memory, to see the results, or test it, as follows:

static void Main(string[] args)
{
     Bar b1 = new Bar();
     // populate b1 with interesting data
     Bar b2 = new Bar();
     // populate b2 with interesting data

     Foo f = new Foo();
     f.Bars.Add(b1);
     f.Bars.Add(b2);
     f.Something = "My String";

     using (MemoryStream ms = new MemoryStream())
     using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(ms))
     {
         System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo));
         serializer.Serialize(textWriter, f);
         string text = Encoding.UTF8.GetString(ms.ToArray());
         Console.WriteLine(text);
     }

    Console.ReadKey(false);
}

To serialize using interfaces, use my project XmlSerialization

like image 24
Artur Mustafin Avatar answered Sep 30 '22 17:09

Artur Mustafin