How to define multiple names for XmlElement field?

自闭症网瘾萝莉.ら 提交于 2019-11-27 02:04:24

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)

If you need only one more name, here is a quick (and rather ugly) solution that we deployed in several cases in my work when we had only to read XMLs (this will be problematic for serializing back to an XML), because it's the simplest and easiest to understand:

[XmlRoot]
public class SomeAccount
{
    [XmlElement("parentId")]
    public long ParentId { get; set; }
    [XmlElement("LeParentId")]
    public long LeParentId { get { return this.ParentId; } set { this.ParentId = value; } }
    //rest of fields...
}

I know this is an old post, but maybe this will help anyone else having the same problem. What you could use for this problem is XmlChoiceIdentifier.

[XmlRoot]
public class SomeAccount
{
    [XmlIgnore]
    public ItemChoiceType EnumType;

    [XmlChoiceIdentifier("EnumType")]
    [XmlElement("LeParentId")]
    [XmlElement("parentId")]
    public long ParentId { get; set; }

    //rest of fields...
} 
[XmlType(IncludeInSchema = false)]
public enum ItemChoiceType
{
    LeParentId,
    parentId
}

Now if you have a new xml version and a new XmlElement name you just add that name to the ItemChoiceType enum and a new XmlElement to the property.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!