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());

Leave a Reply

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