Serializing/Deserializing XML Attribute Lists

I’ve gotten so used to working with JSON that I’d almost forgotten what a chore it can be to work with XML. A case in point is the below XML sample that I’m trying to deserialize into a class, essentially I want to capture all the attributes of the “Attribute” elements and eventually save them to a SQL table.


<?xml version="1.0" encoding="UTF-8"?>
<PRODUCT_Secondary_Item_Attributes>
	<Header>
		<MessageID>1</MessageID>
		<MessageCreationDate>10/10/2017</MessageCreationDate>
		<MessageCreationTime>16:03:11</MessageCreationTime>
	</Header>
	<PRODUCT_Secondary_Item_Attributes_Definition>
		<PRODUCT_Secondary_Item_Attributes_Definition_Entry GroupType="Category" TransType="Add" TransactionID="18">
			<CategoryID>2</CategoryID>
			<CategoryName>Stuff</CategoryName>
			<Attributes>
				<Attribute AttributeID="206203" DefiningOrDesc="Descriptive" Name="Name 1" CustomAttribute="2"/>
				<Attribute AttributeID="206003" DefiningOrDesc="Descriptive" Name="Name 2" AnotherCustomAttribute="Here?"/>
				<Attribute AttributeID="165907" DefiningOrDesc="Descriptive" Name="Name 3" YetAnotherCustomAttribute="2" YetAnotherCustomAttribute="19" YetAnotherCustomAttribute="Keyboard"/>
				<Attribute AttributeID="152181" DefiningOrDesc="Descriptive" Name="Name 4" CustomAttribute="2" CustomAttribute79="foo"/>
			</Attributes>
		</PRODUCT_Secondary_Item_Attributes_Definition_Entry>
	</PRODUCT_Secondary_Item_Attributes_Definition>
	<PRODUCT_Secondary_Item_Attributes_Values/>
</PRODUCT_Secondary_Item_Attributes>

These Attributes can contain any number of custom attributes which all need to be captured, in order to do this I created an IXmlSerializable class which trats the attributes as a list of dictionaries.


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

namespace ProductLoading.Helpers
{
    public class AttributeDictionary : IXmlSerializable
    {
        private List<Dictionary<string, string>> attributeDictionary = new List<Dictionary<string, string>>();

        public AttributeDictionary(List<Dictionary<string, string>> dictionary)
        {
            attributeDictionary = dictionary;
        }

        public AttributeDictionary()
        {
            attributeDictionary = null;
        }

        public XmlSchema GetSchema()
        {
            return (null);
        }

        public void ReadXml(XmlReader reader)
        {
            XElement attributesElement = XElement.Parse(reader.ReadOuterXml());
            List<XElement> attributeElements = attributesElement.Elements("Attribute").ToList();
            attributeDictionary = attributeElements.Select(x => x.Attributes().ToDictionary(y => y.Name.LocalName, z => z.Value)).ToList();
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("Attributes");

            foreach (Dictionary<string, string> dictionary in attributeDictionary)
            {
                writer.WriteStartElement("Attribute");

                foreach (KeyValuePair<string, string> keyValuePair in dictionary)
                {
                    writer.WriteAttributeString(keyValuePair.Key, keyValuePair.Value);
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }
    }
}

When creating the class for the input XML to be serialized into we then just declare the “Attributes” element to be of type “AttributeDictionary” and the serializer does the rest.


using ProductLoading.Helpers;

namespace ProductLoading.Models.SecondaryItemAttributes
{
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
    public partial class DefinitionEntry
    {
        public int CategoryID { get; set; }

        public string CategoryName { get; set; }

        public AttributeDictionary Attributes { get; set; }
    }
}

Note!

It turns out that the reader needs to be read to the end to release it, if it’s not it won’t throw an error but just won’t read in anything after it’s triggered. I was previously populating my XElement through the following.


XElement attributesElement = XElement.Load(reader.ReadSubtree());

This creates a copy of the reader and doesn’t actually finish using the original reader (probably obvious in retrospect). In order to fix this I changed it to the following.


XElement attributesElement = XElement.Parse(reader.ReadOuterXml());

4 Comments

Richard Griffiths · 13th September 2019 at 6:27 pm

This post https://blog.bitscry.com/2017/11/21/serializing-deserializing-xml-attribute-lists/ is exactly what I need, unfortunately I am not that C# savvy at all… Do you have the code implementation example as well as I am struggling? Though best endeavours this weekend I will be giving it more of a go so thanks for the post!

    Shinigami · 14th September 2019 at 8:55 am

    Unfortunately I don’t have the code for this available and it’s long enough ago that I’ve pretty much forgotten how it works!

    Good luck with working on it this weekend and if you get stuck feel free to upload a link to a sample GitHub (or similar) project and I’ll take a look when I get s chance.

      Richard Griffiths · 15th September 2019 at 5:52 pm

      Cheers for getting back, after lots of throwing things at console apps this afternoon and not knowing what I adoing (I’m a SQL man by trade!)… I have copped out…
      Not as clever as what you were alluding too but I think I have what I need in XMLSerializer7 consoleapp here for my particular scenario:
      https://github.com/griff182uk/Serialization
      Cheers for getting me started anyway!

        Shinigami · 20th September 2019 at 3:52 pm

        I can’t find my original code for this and I can’t seem to recreate it so as yours seems to work I’d suggest sticking with it 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *