This will deserialize an xml sample into the "XmlModel" class.
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace XmlTest
{
public class DeserializeXml
{
public XmlModel GetXmlModel()
{
string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<root>
<foo>
<bar>1</bar>
<bar>2</bar>
</foo>
</root>";
var dS = new XmlSerializer(typeof(XmlModel));
var m = new XmlModel();
using (var reader = new StringReader(xml))
{
return (XmlModel) dS.Deserialize(reader);
}
}
}
[XmlRoot("root")]
public class XmlModel
{
[XmlArray("foo")]
[XmlArrayItem("bar")]
public List<string> Foo { get; set; }
}
}
This will get the model:
var d = new DeserializeXml();
result = d.GetXmlModel();
I am working with legacy code and I cannot make changes to the XmlModel class other than changing the XmlAttributes. Here is the problem: the actual Xml has no "foo" node:
string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<root>
<bar>1</bar>
<bar>2</bar>
</root>";
So now I am stuck with the task of making the deserializer swallow this xml and output type XmlModel. Is this possible without Xslt preprocessing or other more complicated methods?
You can use XmlAttributeOverrides
to specify alternate XML attributes for your XmlModel
, then construct an XmlSerializer
using those attributes by doing:
var serializer = new XmlSerializer(typeof(XmlModel), overrides).
However, note the following warning from the documentation:
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable...
The following static class creates and caches 2 serializers, one for the "current" version of XmlModel
, and one for the "alternate" version in which the <bar>
elements lack an outer container element:
public static class XmlModelSerializer<TRoot>
{
static XmlSerializer alternateSerializerInstance;
static XmlSerializer currentSerializerInstance;
public static XmlSerializer AlternateSerializerInstance { get { return alternateSerializerInstance; } }
public static XmlSerializer CurrentSerializerInstance { get { return currentSerializerInstance; } }
static XmlModelSerializer()
{
XmlAttributes alternateAttributes = new XmlAttributes
{
XmlElements = { new XmlElementAttribute("bar") },
};
XmlAttributeOverrides alternateOverrides = new XmlAttributeOverrides();
alternateOverrides.Add(typeof(XmlModel), "Foo", alternateAttributes);
alternateSerializerInstance = new XmlSerializer(typeof(TRoot), alternateOverrides);
XmlAttributes currentAttributes = new XmlAttributes
{
XmlArray = new XmlArrayAttribute("foo"),
XmlArrayItems = { new XmlArrayItemAttribute("bar") },
};
XmlAttributeOverrides currentOverrides = new XmlAttributeOverrides();
currentOverrides.Add(typeof(XmlModel), "Foo", currentAttributes);
currentSerializerInstance = new XmlSerializer(typeof(TRoot), currentOverrides);
}
}
By using two different serializers, one for each possible XML format, you can avoid making any changes at all to your legacy XmlModel
type.
Then, to deserialize flattened XML of the form
<root>
<bar>1</bar>
<bar>2</bar>
</root>
You would simply do:
var dS = XmlModelSerializer<XmlModel>.AlternateSerializerInstance;
using (var reader = new StringReader(xml))
{
return (XmlModel) dS.Deserialize(reader);
}
Sample fiddle showing deserialization in both formats.