Group group c# class properties into XmlElement attributes during serialization

372 Views Asked by At

During the serialization of c# classes into xml, I need to match a certain xml structure, but it would be for the best not to alter the class structure already present.

Is it possible to create a sort of grouping of a number of properties into their own xml element where they store their values as xml attributes?

An example c# code would be something like:

class SomeClass {
    [XmlElement("Element1")]
    [XmlAttribute("attribute1")]
    int prop1;
    [XmlElement("Element1")]
    [XmlAttribute("attribute2")]
    int prop2;

    [XmlElement("Element2")]
    [XmlAttribute("attribute1")]
    int prop3;
    [XmlElement("Element2")]
    [XmlAttribute("attribute2")]
    int prop4;
    [XmlElement("Element2")]
    [XmlAttribute("attribute3")]
    int prop5;
    [XmlElement("Element2")]
    [XmlAttribute("attribute4")]
    int prop6;
}

With an xml output of:

<SomeClass>
    <Element1 Attribute1="value1" attribute2="value2"/>
    <Element2 Attribute1="value3" attribute2="value4" attribute3="value5" attribute4="value6"/>
</SomeClass>

after serialization.

It would be amazing if the solution worked for deserialization as well.

2

There are 2 best solutions below

0
On BEST ANSWER

If you really must avoid "altering the class structure", attributes are (in my opinion) not going to get the XML structure you would like and help you get readable, maintainable code.

The IXmlSerializable interface allows you to manually control serialization/deserialization of your class. When implementing this interface, you have full control to create whatever XML text you desire when your class is consumed by XmlSerializer.

Below is a compiling console app example for you to play with. It shows how to read and write custom nodes within your SomeClass XML, and putting your class properties into XML attributes on those nodes. Note I used nameof to get an attribute name, but you could just as easily hard-code the XML attribute names to "Attribute1", "Attribute2" if you wanted.

As you will see, it's more cumbersome to write than using attributes. But it can be pretty straightforward once you get the hang of it. And it definitely doesn't change the class structure.

using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    public class SomeClass : IXmlSerializable
    {
        // element 1
        public int prop1 { get; set; }
        public int prop2 { get; set; }

        // element 2
        public int prop3 { get; set; }
        public int prop4 { get; set; }
        public int prop5 { get; set; }
        public int prop6 { get; set; }

        #region IXmlSerializable
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("Element1");
            writer.WriteAttributeString(nameof(prop1), prop1.ToString());
            writer.WriteAttributeString(nameof(prop2), prop2.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("Element2");
            writer.WriteAttributeString(nameof(prop3), prop3.ToString());
            writer.WriteAttributeString(nameof(prop4), prop4.ToString());
            writer.WriteAttributeString(nameof(prop5), prop5.ToString());
            writer.WriteAttributeString(nameof(prop6), prop6.ToString());
            writer.WriteEndElement();
        }

        public void ReadXml(XmlReader reader)
        {
            // element 1
            reader.Read();
            reader.MoveToAttribute(nameof(prop1));
            if (reader.ReadAttributeValue())
            {
                prop1 = int.Parse(reader.Value);
            }
            reader.MoveToAttribute(nameof(prop2));
            if (reader.ReadAttributeValue())
            {
                prop2 = int.Parse(reader.Value);
            }

            // element 2
            reader.Read();
            reader.MoveToAttribute(nameof(prop3));
            if (reader.ReadAttributeValue())
            {
                prop3 = int.Parse(reader.Value);
            }
            reader.MoveToAttribute(nameof(prop4));
            if (reader.ReadAttributeValue())
            {
                prop4 = int.Parse(reader.Value);
            }
            reader.MoveToAttribute(nameof(prop5));
            if (reader.ReadAttributeValue())
            {
                prop5 = int.Parse(reader.Value);
            }
            reader.MoveToAttribute(nameof(prop6));
            if (reader.ReadAttributeValue())
            {
                prop6 = int.Parse(reader.Value);
            }
        }
        #endregion
    }

    class Program
    {
        static void Main()
        {
            string tempPath = "c:\\temp\\test.xml";
            XmlSerializer serializer = new XmlSerializer(typeof(SomeClass));

            // build an instance to serialize
            SomeClass s1 = new SomeClass
            {
                prop1 = 1,
                prop2 = 2,
                prop3 = 3,
                prop4 = 4,
                prop5 = 5,
                prop6 = 6
            };

            // serialize it
            using (StreamWriter sw = new StreamWriter(tempPath))
            {
                serializer.Serialize(sw, s1);

            }

            /* Produces the following XML:
                <?xml version="1.0" encoding="utf-8"?>
                <SomeClass>
                  <Element1 prop1="1" prop2="2" />
                  <Element2 prop3="3" prop4="4" prop5="5" prop6="6" />
                </SomeClass>
            */

            // deserialize
            SomeClass s2;
            using (StreamReader sr = new StreamReader(tempPath))
            {
                s2 = (SomeClass)serializer.Deserialize(sr);
            }

            // check contents of s2 as you please
        }
    }
}

(If you like this, you should clean it up before actually deploying it - there's no error handling present, for example on int.Parse. It's just meant to illustrate the use of IXmlSerializable without changing the class structure at all.)

0
On

You can do this by using separate classes for each XmlElement:

public class SomeClass 
{
    public Element1 Element1 { get; set; }
    
    public Element2 Element2 { get; set; }
}

public class Element1 
{
    [XmlAttribute]
    public int attribute1 { get; set; }

    [XmlAttribute]
    public int attribute2 { get; set; }
}

public class Element2
{
    [XmlAttribute]
    public int attribute1 { get; set; }

    [XmlAttribute]
    public int attribute2 { get; set; }
    
    [XmlAttribute]
    public int attribute3 { get; set; }

    [XmlAttribute]
    public int attribute4 { get; set; }
}

Online demo