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.

Original source

Related problems