Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize a polymorphic List with the same type name

I have an object I'm trying to serialize into XML. Inside this object is a list of a generic type (abstract class). Each item in this list could be a different class, but all are inheriting from the abstract base class:

public abstract class animal
{
  public string type { get; set; }
}
public class cat:animal
{
  public string size { get; set; }
  public string furColor { get; set; }
}
public class fish:animal
{
  public string size { get; set; }
  public string scaleColor { get; set; }
}

When I serialize the list, I want it to look like this:

<animal type="cat">
  <size>medium</size>
  <furColor>black</furColor>
</animal>
<animal type="fish">
  <size>small</size>
  <scaleColor>silver</scaleColor>
</animal>

I've tried the simple solution:

[XmlElement("Animal")]
public List<animal> Animals { get; set; }

But it throws an error because it's not expecting the object type "cat." Adding the [XmlInclude] tag to either the base class, the derived class, or the whole containing class (let's call it zoo) does not help this.

I can use the typeof designation for a single class:

[XmlElement("Animal", typeof(cat))]
public List<animal> Animals { get; set; }

and this works properly, as I want it to, as long as I only use cats. Again, the minute I add a fish to the mix, it blows up with the same error (not expecting fish).

I can add multiple typeof attributes:

[XmlElement("Animal")]
[XmlElementAttribute(typeof(cat))]
[XmlElementAttribute(typeof(fish))]
public List<animal> Animals { get; set; }

and this compiles, but ignores the element name, and serializes the objects as <cat> </cat> and <fish> </fish>, respectively, which is unacceptable.

I've even tried adding multiple [XmlElement] tags:

[XmlElement("Animal", typeof(cat))]
[XmlElement("Animal", typeof(fish))]
public List<animal> Animals { get; set; }

This one throws a different exception, this time that the objects "cat" and "fish" both use the type "Animal" in the same scope.

Can anyone think of a way around this?

UPDATE After a little more digging, I found This SO post which suggests adding the namespace to the base class:

[XmlRoot(Namespace="myNamespace")]
[XmlInclude(typeof(cat))]
[XmlInclude(typeof(fish))]
public abstract class animal

Serializing this yields the following:

<animal xsi:type="cat" type="cat">
  ...
</animal>
<animal xsi:type="fish" type="fish">
  ...
</animal>

Where xsi:type="cat" refers to the name of the class, and type="cat" refers to the type attribute created within the base class (see the very top example). This is so close to what I need, and I fear I may just be suffering from inexperience here, but is there a way to get rid of the xsi:type attribute listing?

like image 490
JesseF Avatar asked Feb 13 '13 22:02

JesseF


3 Answers

Adding the XmlInclude Attribute to your base class should solve this serilazation issue

    [Serializable]
    [XmlInclude(typeof(cat))]
    [XmlInclude(typeof(fish))]
    class animals
    {
         ....
    }

    public class cat:animals
    {
      public string size { get; set; }
      public string furColor { get; set; }
    }

    public class fish:animals
    {
      public string size { get; set; }
      public string scaleColor { get; set; }
    }

UPDATE

Here's a brief example that works on my side, you don't actually need the type attribute to my opinion because you can retrieve it with typeof() method. unless the type attribute had another purpose.

        List<animal> animals = new List<animal>();
        cat catofdoom = new cat();
        catofdoom.furColor = "brown";
        catofdoom.size = "10 pounds";
        animals.Add(catofdoom);

        fish fishofdoom = new fish();
        fishofdoom.scaleColor = "blue";
        fishofdoom.size = "12 inches";
        animals.Add(fishofdoom);


        try
        {
            XmlSerializer xs = new XmlSerializer(typeof(List<animal>));
            using (StreamWriter wr = new StreamWriter("animal.xml"))
            {
                xs.Serialize(wr, animals);
            }
        }
        catch (Exception e)
        {

            throw;
        }

results into this ( very basic serialization with no options ) :

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfAnimal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <animal xsi:type="cat">
    <size>10 pounds</size>
    <furColor>brown</furColor>
  </animal>
  <animal xsi:type="fish">
    <size>12 inches</size>
    <scaleColor>blue</scaleColor>
  </animal>
</ArrayOfAnimal>
like image 53
legrandviking Avatar answered Oct 12 '22 23:10

legrandviking


You need both!

[Serializable]
[XmlInclude(typeof(cat))]
[XmlInclude(typeof(fish))]
class animals
{
     ....
}

public class cat:animals
{
  public string size { get; set; }
  public string furColor { get; set; }
}

public class fish:animals
{
  public string size { get; set; }
  public string scaleColor { get; set; }
}

Note the slight change to the XmlElement name. This will work correctly now.

[XmlElement("cat", typeof(cat))]
[XmlElement("fish", typeof(fish))]
public List<animal> Animals { get; set; }
like image 26
Zev Avatar answered Oct 13 '22 00:10

Zev


I suggest using the DataContractSerializer and include the known types into the serializer. Using XmlInclude is kind of a break for encasulation principle. See the provided MSDN link for some samples...

like image 38
Jaster Avatar answered Oct 13 '22 00:10

Jaster