How to define multiple names for XmlElement field?

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)

Leave a Comment