1

Given the following XML I want to deserialize:

<?xml version="1.0" encoding="utf-8" ?>
<units>      
  <entity>
    <health max="1000"/>   
    <sprite texture="tank"/>
    <entity>        
      <sprite texture="tank-turret"/> <!-- this element is missing when i deserialize --!>
    </entity>    
  </entity>         
</units>

How can I deserialize this recursive object graph using XmlSerializer?

The following is my last try. It successfully deserializes the top-level objects (health, sprite, entity) but it does not seem to find the sprite element in the nested entity node. I also tried deriving entity from componentlist, but it didn't work either.

public class UnitSerializer
{
    public abstract class item
    {
    }

    public class entity : item
    {
        [XmlArray("entity")]
        [XmlArrayItem(typeof(health))]
        [XmlArrayItem(typeof(entity))]
        [XmlArrayItem(typeof(sprite))]
        public componentlist entity2 { get; set; }
    }

    public abstract class component : item
    {
    }

    public class health : component
    {
        [XmlAttribute]
        public int max { get; set; }
    }

    public class sprite : component
    {
        [XmlAttribute]
        public string texture { get; set; }

    }

    public class componentlist : List<item>
    {
    }

    [XmlRoot("units")]
    public class units
    {
        [XmlArray("entity")]
        [XmlArrayItem(typeof(health))]
        [XmlArrayItem(typeof(entity))]
        [XmlArrayItem(typeof(sprite))]
        public componentlist entity { get; set; }
    }

    public void Read()
    {
        var x = new XmlSerializer(typeof(units),
            new[] {
                        typeof(componentlist),
                        typeof(entity),
                        typeof(health),
                        typeof(sprite)
                });
        var fs = new FileStream("units.xml", FileMode.Open);
        XmlReader reader = new XmlTextReader(fs);
        var units = (units)x.Deserialize(reader);
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
codymanix
  • 28,510
  • 21
  • 92
  • 151

1 Answers1

1

Your classes can be fixed by replacing use of [XmlArray] and [XmlArrayItem] with [XmlElement(typeof(TDerived))]:

public class UnitSerializer
{
    public abstract class item
    {
    }

    public class entity : item
    {
        [XmlElement("health", typeof(health))]
        [XmlElement("entity", typeof(entity))]
        [XmlElement("sprite", typeof(sprite))]
        public List<item> EntityList { get; set; }
    }

    public abstract class component : item
    {
    }

    public class health : component
    {
        [XmlAttribute]
        public int max { get; set; }
    }

    public class sprite : component
    {
        [XmlAttribute]
        public string texture { get; set; }
    }

    [XmlRoot("units")]
    public class units
    {
        [XmlElement("health", typeof(health))]
        [XmlElement("entity", typeof(entity))]
        [XmlElement("sprite", typeof(sprite))]
        public List<item> EntityList { get; set; }
    }

    public units Read(string filename)
    {
        var x = new XmlSerializer(typeof(units));
        using (var fs = new FileStream(filename, FileMode.Open))
        using (var reader = XmlReader.Create(fs))
        {
            return (units)x.Deserialize(reader);
        }
    }
}

Notes:

  • [XmlArray] indicates a collection should be serialized with an outer wrapper element containing a sequence of elements, while [XmlElement] indicates a collection should be serialized as a sequence without the wrapper. Your XML sample uses repeating elements without wrapper elements, so [XmlElement] should be used. It sort of works because your XML is recursive -- but at every other level repeating elements are getting incorrectly deserialized as wrappers. This explains why some but not all data is lost during deserialization.

  • In your XML sample, polymorphic elements are identified by element name. The XmlSerializer(Type, Type[]) constructor should be used to specify polymorphic included types to be serialized using the xsi:type mechanism. Since the xsi:type attribute does not appear in your XML, this constructor need not be used.

    (In addition, when constructing an XmlSerializer using the XmlSerializer(Type, Type[]) constructor, you must cache the serializer statically to avoid a severe memory leak. See Memory Leak using StreamReader and XmlSerializer for why.)

  • XmlTextReader has been deprecated since .Net 2.0. Use XmlReader.Create() instead.

  • The FileStream and XmlReader should be disposed, ideally via using statements.

  • I eliminated the public class componentlist : List<item> and replaced it with just a List<item>. This was mainly a matter of taste, but it does make it easier to set the value of such a list using Linq's .ToList().

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    thank you for the excellent and exhausting answer! I also now could get rid of the duplicate EntityList property definition by ineriting units from entity . – codymanix Mar 28 '21 at 16:43