I have a class \'products\' that is serializable to XML. I\'m using the standard System.Xml.Serialization.XmlSerializer to serialize and a XmlWriter \'writer\' object to write t
Assuming that your dictionary value are all simple types that can be converted to a string, you can create your own IXmlSerializable
dictionary wrapper to store and retrieve the keys and values:
public class XmlKeyTextValueListWrapper : CollectionWrapper>, IXmlSerializable
{
public XmlKeyTextValueListWrapper() : base(new List>()) { } // For deserialization.
public XmlKeyTextValueListWrapper(ICollection> baseCollection) : base(baseCollection) { }
public XmlKeyTextValueListWrapper(Func>> getCollection) : base(getCollection) {}
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.ReadXml(reader, this, converter);
}
public void WriteXml(XmlWriter writer)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.WriteXml(writer, this, converter);
}
#endregion
}
public static class XmlKeyValueListHelper
{
public static void WriteXml(XmlWriter writer, ICollection> collection, TypeConverter typeConverter)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
}
public static void ReadXml(XmlReader reader, ICollection> collection, TypeConverter typeConverter)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var key = XmlConvert.DecodeName(reader.Name);
string value;
if (reader.IsEmptyElement)
{
value = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
value = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair(key, (T)typeConverter.ConvertFromInvariantString(value)));
}
// Move past the end of the list element
reader.ReadEndElement();
}
public static void CopyTo(this XmlKeyTextValueListWrapper collection, ICollection> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (collection == null)
dictionary.Clear();
else
{
if (collection.IsWrapperFor(dictionary)) // For efficiency
return;
var pairs = collection.ToList();
dictionary.Clear();
foreach (var item in pairs)
dictionary.Add(item);
}
}
}
public class CollectionWrapper : ICollection
{
readonly Func> getCollection;
public CollectionWrapper(ICollection baseCollection)
{
if (baseCollection == null)
throw new ArgumentNullException();
this.getCollection = () => baseCollection;
}
public CollectionWrapper(Func> getCollection)
{
if (getCollection == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
}
public bool IsWrapperFor(ICollection other)
{
if (other == Collection)
return true;
var otherWrapper = other as CollectionWrapper;
return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
}
ICollection Collection { get { return getCollection(); } }
#region ICollection Members
public void Add(T item)
{
Collection.Add(item);
}
public void Clear()
{
Collection.Clear();
}
public bool Contains(T item)
{
return Collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Collection.Count; }
}
public bool IsReadOnly
{
get { return Collection.IsReadOnly; }
}
public bool Remove(T item)
{
return Collection.Remove(item);
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return Collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
And then use it like so:
[XmlRoot("products")]
public class Products
{
public Products()
{
Specifications = new Dictionary();
}
[XmlIgnore]
[JsonProperty("specifications")] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public Dictionary Specifications { get; set; }
[XmlElement("specifications")]
[JsonIgnore] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public XmlKeyTextValueListWrapper XmlSpecifications
{
get
{
return new XmlKeyTextValueListWrapper(() => this.Specifications);
}
set
{
value.CopyTo(Specifications = (Specifications ?? new Dictionary()));
}
}
}
The fact that your dictionary values are simple types (directly convertible from and to text) makes it possible to avoid nested creations of XmlSerializer
, which is more complex. See here for an example.