Paul
Paul

Reputation: 3253

Serialize list of interfaces using XML Serialization

I have the class below which I need to serialize using the XML Serializer

I know that the XML serializer does not like interfaces

However, our coding standards normally involve always coding to interfaces not concrete classes

I thought of a way to be able to stream this list without changing the types which would cause many compilation issues, as well as breaking our standards

My idea is to have a property which reads the Meters list and casts to concrete classes then to totally ignore meters when it comes to serializing

However, the XML will still need to have Meters as the element name

However this does not work

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

    public class MeterWalkOrder
    {
        public MeterWalkOrder()
        {
            Meters = new List<IMeter>();
        }

        public String Name { get; set; }


        [XmlIgnore]
        public List<IMeter> Meters { get; set; }

        [XmlArrayItem(ElementName = "Meter")]
        [XmlElement(ElementName = "Meters")]
        public List<Meter> SerializableMeters
        {
            get
            {
                return Meters.Cast<Meter>().ToList();
            }
            set
            {
                Meters = new List<IMeter>(value);                
            }
        }
    }
}

Is there any way around this?

My XML (which cannot be changed) is below

   <MeterWalkOrder>
      <Name>Red Route</Name>
      <Meters>
        <Meter>
          <MeterID>1</MeterID>
          <SerialNumber>12345</SerialNumber>
        </Meter>
        <Meter>
          <MeterID>2</MeterID>
          <SerialNumber>SE</SerialNumber>
        </Meter>
      </Meters>
    </MeterWalkOrder>

The top level error is

There was an error reflecting type 'WindowsFormsApplication1.Classes.MeterWalkOrder'.

The inner exception is

{"XmlElement, XmlText, and XmlAnyElement cannot be used in conjunction with XmlAttribute, XmlAnyAttribute, XmlArray, or XmlArrayItem."}

I really would like to avoid setting a precedent for using lists of concrete classes

Upvotes: 1

Views: 2911

Answers (1)

sa_ddam213
sa_ddam213

Reputation: 43596

You will need to replace [XmlElement(ElementName = "Meters")] with [XmlArray(ElementName = "Meters")] as the error states:

{"XmlElement, XmlText, and XmlAnyElement cannot be used in conjunction with XmlAttribute, XmlAnyAttribute, XmlArray, or XmlArrayItem."}

To define an Array for the XmlSerializer you can mark it with XmlArray and its corresponding items with XmlArrayItem

Example:

public class MeterWalkOrder
{
    public MeterWalkOrder()
    {
        Meters = new List<IMeter>();
    }

    public String Name { get; set; }

    [XmlIgnore]
    public List<IMeter> Meters { get; set; }

    [XmlArrayItem(ElementName = "Meter")]
    [XmlArray(ElementName = "Meters")]
    public List<Meter> SerializableMeters
    {
        get
        {
            return Meters.Cast<Meter>().ToList();
        }
        set
        {
            Meters = new List<IMeter>(value);                
        }
    }
}

 public interface IMeter {
   int MeterID { get; set; }
 }

 public class Meter : IMeter {
     public int MeterID { get; set; }
     public string SerialNumber { get; set; }
 }

Test:

var data = new MeterWalkOrder{ Name = "Red Route", Meters = new List<IMeter>
{
     new Meter{ MeterID = 1, SerialNumber = "12345"},
     new Meter{ MeterID = 2, SerialNumber = "SE"}
}};
using (var stream = new FileStream("C:\\Users\\Adam\\Desktop\\Test2.xml", FileMode.OpenOrCreate))
{
    XmlSerializer ser = new XmlSerializer(typeof(MeterWalkOrder));
    ser.Serialize(stream, data);
}

Result:

<?xml version="1.0"?>
<MeterWalkOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Red Route</Name>
  <Meters>
    <Meter>
      <MeterID>1</MeterID>
      <SerialNumber>12345</SerialNumber>
    </Meter>
    <Meter>
      <MeterID>2</MeterID>
      <SerialNumber>SE</SerialNumber>
    </Meter>
  </Meters>
</MeterWalkOrder>

Upvotes: 10

Related Questions