Serializing elements in collection with their concret class name

May 24, 2011 at 10:00 AM

Hi,

I have a xml document, provided from an external partner, which has a bit of a crazy heirachy. I have a simplified example of it here:

<?xml version="1.0" encoding="utf-8"?>
<Engine>
  <Cylinder>
    <Volume>0</Volume>
  </Cylinder>
  <Battery>
    <Capacity>0</Capacity>
  </Battery>
</Engine>

And here is the equivalent code, a want to be able to serialize and deserialize to and from:

public class Engine
{
    public Engine()
    {
        EngineParts = new List<EnginePart>();
    }

    [YAXCollection(YAXCollectionSerializationTypes.RecursiveWithNoContainingElement)]
    public List<EnginePart> EngineParts { get; set; }
}

public abstract class EnginePart
{
    [YAXAttributeForClass]
    public string Id { get; set; }
}

public class Cylinder : EnginePart
{
    public int Volume { get; set; }
}

public class Battery : EnginePart
{
    public int Capacity { get; set; }
}

As you can see both Cylinder and Battery inherits from EnginePart. There can be a lot of different EngineParts, and also multiple of the same type added at the same time.

My problem is that YAX expects and realtype attribute, in order to identify a concret EnginePart class, e.g.:

<?xml version="1.0" encoding="utf-8"?>
<Engine xmlns:yaxlib="http://www.sinairv.com/yaxlib/">
  <EnginePart Id="" yaxlib:realtype="Playground.Cylinder">
    <Volume>0</Volume>
  </EnginePart>
  <EnginePart Id="" yaxlib:realtype="Playground.Battery">
    <Capacity>0</Capacity>
  </EnginePart>
</Engine>

Is there a way to tell the YAXSerializer to use the class name of the derived type, so when it sees an element with the name <Cylinder> it knows it should use the type Playground.Cylinder?

Kind regards,
Lars

ps. thanks for making a serializer which is easy to use and customize

May 25, 2011 at 8:02 AM

I have almost found a solution :-)
It might help somebody, here is my new code:

    public class EnginePartsSerializer : ICustomSerializer<List<EnginePart>>, ICustomDeserializer<List<EnginePart>>
    {
        public void SerializeToElement(List<EnginePart> objectToSerialize, XElement elemToFill)
        {
            foreach (var enginePart in objectToSerialize)
            {
                var xDocument = new YAXSerializer(enginePart.GetType()).SerializeToXDocument(enginePart);
                elemToFill.Add(xDocument.Root);
            }
        }

        public List<EnginePart> DeserializeFromElement(XElement element)
        {
            var someEnginePartType = typeof(Cylinder);
            var engineParts = new List<EnginePart>();
            foreach (var enginePartElement in element.Elements())
            {
                var enginePartType = someEnginePartType.Assembly.GetType(someEnginePartType.Namespace + enginePartElement.Name, true, true);
                engineParts.Add((EnginePart)new YAXSerializer(enginePartType).Deserialize(enginePartElement));
            }

            return engineParts;
        }

        public void SerializeToAttribute(List<EnginePart> objectToSerialize, XAttribute attrToFill)
        {
            throw new NotImplementedException();
        }

        public string SerializeToValue(List<EnginePart> objectToSerialize)
        {
            throw new NotImplementedException();
        }

        public List<EnginePart> DeserializeFromAttribute(XAttribute attrib)
        {
            throw new NotImplementedException();
        }

        public List<EnginePart> DeserializeFromValue(string value)
        {
            throw new NotImplementedException();
        }
    }

    public class Engine
    {
        public Engine()
        {
            EngineParts = new List<EnginePart>();
        }

        // TODO: Can not combine RecursiveWithNoContainingElement with YAXCustomSerializer
        //[YAXCollection(YAXCollectionSerializationTypes.RecursiveWithNoContainingElement)]
        [YAXCustomSerializer(typeof(EnginePartsSerializer))]
        public List<EnginePart> EngineParts { get; set; }
    }
And the resulting XML:
<?xml version="1.0" encoding="utf-8"?>
<Engine>
  <EngineParts>
    <Cylinder Id="">
      <Volume>0</Volume>
    </Cylinder>
    <Battery Id="">
      <Capacity>0</Capacity>
    </Battery>
  </EngineParts>
</Engine>
My only problem know is that im unable to combine this solution with having a collection with no containing element.
But i hope this helps someone.
/Lars 
 
Coordinator
May 25, 2011 at 9:32 AM

Dear Lars,

Thanks for suggesting this feature. I made an update to the library, so that now it serializes this sort of objects with their class name or user defined aliases, instead of the base class name. Please download the most recent source version from the repository and let me know if it is what you want. Actually the yaxlib:realtype attribute cannot be removed, because the deserializer will not be able to detect the underlying type without it. But anyway the modified element names make the resulting xml more readable.