Search code examples
c#xml-serialization

custom Xml Serializer to make value as an xml element


Classes:

public class Employee
{
    public int Id { get; set; }

    public PhoneNumber[] Numbers { get; set; }
}

public class PhoneNumber
{
    public string Type { get; set; }

    public string Number { get; set; }
}

Code to serialize:

var xmlSerializer = new XmlSerializer(typeof(Employee));

var xwSettings = new XmlWriterSettings {Indent = true, OmitXmlDeclaration = true};

string serializedResult;
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, xwSettings))
{
    xmlSerializer.Serialize(writer, emp);
    serializedResult = stream.ToString();
}

Current Result:

<Employee>
  <Id>1</Id>
  <Numbers>
    <PhoneNumber>
      <Type>Home</Type>
      <Number>1231231231</Number>
    </PhoneNumber>
    <PhoneNumber>
      <Type>Office</Type>
      <Number>3453453453</Number>
    </PhoneNumber>
  </Numbers>
</Employee>

Desired Result:

<Employee>
  <Id>1</Id>
  <Numbers>
    <Home>1231231231</Home>
    <Office>3453453453</Office>
  </Numbers>
</Employee>

PhoneNumber Type can be added dynamically like "GuestRoomPhone" etc, so adding properties for each phone number type is not an option.


Solution

  • You can do this by implementing the IXmlSerializable interface on your classes. This allows you to control how the values are written and read.

    public class Employee : IXmlSerializable
    {
        public int Id { get; set; }
    
        public PhoneNumber[] Numbers { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null;
        }
    
        public void ReadXml(XmlReader reader)
        {
            reader.ReadStartElement("Employee");
            reader.ReadStartElement("Id");
            Id = reader.ReadContentAsInt();
            reader.ReadEndElement();    // Id
    
            reader.ReadStartElement("Numbers");
    
            List<PhoneNumber> numbers = new List<PhoneNumber>();
            while (reader.MoveToContent() == XmlNodeType.Element)
            {
                PhoneNumber num = new PhoneNumber();
                num.ReadXml(reader);
                numbers.Add(num);
            }
            Numbers = numbers.ToArray();
    
            reader.ReadEndElement();    // Numbers
            reader.ReadEndElement();    // Employee
        }
    
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("Id");
            writer.WriteValue(Id);
            writer.WriteEndElement();
    
            writer.WriteStartElement("Numbers");
    
            foreach (PhoneNumber num in Numbers)
            {
                num.WriteXml(writer);
            }
    
            writer.WriteEndElement();   // Numbers
        }
    }
    

    Similarly for the PhoneNumber class.

    public class PhoneNumber : IXmlSerializable
    {
        public string Type { get; set; }
        public string Number { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null;
        }
    
        public void ReadXml(XmlReader reader)
        {
            while (!reader.IsStartElement())
                reader.Read();
            Type = reader.Name;
            Number = reader.ReadElementContentAsString();
        }
    
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement(Type);
            writer.WriteString(Number);
            writer.WriteEndElement();
        }
    }