How to define multiple names for XmlElement field?
c#, xml, xml-deserialization
Solution
Take 2 - let's implement this ourselves using the unknown element handling event (see the comments below for some limitations though):
public class XmlSynonymDeserializer : XmlSerializer
{
public class SynonymsAttribute : Attribute
{
public readonly ISet<string> Names;
public SynonymsAttribute(params string[] names)
{
this.Names = new HashSet<string>(names);
}
public static MemberInfo GetMember(object obj, string name)
{
Type type = obj.GetType();
var result = type.GetProperty(name);
if (result != null)
return result;
foreach (MemberInfo member in type.GetProperties().Cast<MemberInfo>().Union(type.GetFields()))
foreach (var attr in member.GetCustomAttributes(typeof(SynonymsAttribute), true))
if (attr is SynonymsAttribute && ((SynonymsAttribute)attr).Names.Contains(name))
return member;
return null;
}
}
public XmlSynonymDeserializer(Type type)
: base(type)
{
this.UnknownElement += this.SynonymHandler;
}
public XmlSynonymDeserializer(Type type, XmlRootAttribute root)
: base(type, root)
{
this.UnknownElement += this.SynonymHandler;
}
protected void SynonymHandler(object sender, XmlElementEventArgs e)
{
var member = SynonymsAttribute.GetMember(e.ObjectBeingDeserialized, e.Element.Name);
Type memberType;
if (member != null && member is FieldInfo)
memberType = ((FieldInfo)member).FieldType;
else if (member != null && member is PropertyInfo)
memberType = ((PropertyInfo)member).PropertyType;
else
return;
if (member != null)
{
object value;
XmlSynonymDeserializer serializer = new XmlSynonymDeserializer(memberType, new XmlRootAttribute(e.Element.Name));
using (System.IO.StringReader reader = new System.IO.StringReader(e.Element.OuterXml))
value = serializer.Deserialize(reader);
if (member is FieldInfo)
((FieldInfo)member).SetValue(e.ObjectBeingDeserialized, value);
else if (member is PropertyInfo)
((PropertyInfo)member).SetValue(e.ObjectBeingDeserialized, value);
}
}
}
And now the actual code of the class would be:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
[XmlSynonymDeserializer.Synonyms("LeParentId", "AnotherGreatName")]
public long ParentId { get; set; }
//rest of fields...
}
To deserialize, simply use `XmlSynonymDeserializer` instead of the regular `XmlSerializer`. This should work for most of the basic needs.
Known limitations:
- This implementation supports only elements with multiple names; extending it for attributes should be trivial
- Support for handling of properties/fields in cases where the entities inherit from one another is not tested
- This implementation does not check for programming bugs (having the attribute on read-only/constant field/properties, multiple members with the same synonyms and so on)
Problem
I have a XML document provided by client applications to my C# application. This is how a client sends the XML file: ``` <?xml version="1.0" encoding="utf-8"?> <SomeAccount> <parentId>2380983</parentId> <!-- more elements --> </SomeAccount> ``` And a C# class that supports the XML deserialization: ``` [XmlRoot] public class SomeAccount { [XmlElement("parentId")] public long ParentId { get; set; } //rest of fields... } ``` But there are some clients whose system send the XML in this way (note the upper case in `LeParentId`): ``` <?xml version="1.0" encoding="utf-8"?> <SomeAccount> <LeParentId>2380983</LeParentId> <!-- similar for the other elements --> </SomeAccount> ``` How can I make this field (and others) to support both XML names `parentId` and `LeParentId`? This is the method I'm currently using for XML deserialization: ``` public sealed class XmlSerializationUtil { public static T Deserialize<T>(string xml) { if (xml == null) return default(T); XmlSerializer serializer = new XmlSerializer(typeof(T)); StringReader stringReader = new StringReader(xml); return (T)serializer.Deserialize(stringReader); } } ``` I tried to add `[XmlElement]` twice in the field, one per element name, but that didn't work.