使用XmlSerializer反序列化XML,其中XmlElement名称不同但内容相同

时间:2017-08-19 00:38:42

标签: c# xml serialization xmlserializer

我想将XML文件反序列化为具有多个子类的类。 XML看起来像这样:

<?xml version="1.0" encoding="utf-8"?>
<Objects>
    <Group index="1">
        <de>
            <GroupName>ANTRIEB</GroupName>
        </de>
        <en>
            <GroupName>missing translation!</GroupName>
        </en>
        <Level>2</Level>
    </Group>
    <Group index="2">
        <de>
            <GroupName>BREMSEN</GroupName>
        </de>
        <Level>3</Level>
    </Group>
</Objects>

如果不存在那些语言标记,则将XML反序列化为类将没有问题。当然,我可以为每种语言标签创建一个属性。但是可能的语言列表应该是动态的(例如从配置文件中读取)。

这就是为什么我想将这些语言标签及其内容反序列化为使用该语言作为关键字和内容模型的词典。

我的模特看起来像这样:

[XmlRoot("Objects")]
public class DeactivationsXml
{
    [XmlElement("Group")]
    public DeactivationsGroup[] Groups { get; set; }
}

[Serializable()]
public class DeactivationsGroup
{
    [XmlIgnore]
    public Dictionary<string, GroupName> GroupNames { get; set; } = new Dictionary<string, GroupName>();

    public int Level { get; set; }

    [XmlAttribute]
    public byte index { get; set; }
}

public class GroupName
{
    [XmlElement("GroupName")]
    public string Name { get; set; }
}

我搜索了很长时间来解决这个问题,但找不到解决方案。我很确定,只有属性才能解决这个问题。

是否存在一些混合方法,以便将XML文件的反序列​​化与所有无法自动反序列化的XmlElements的手动反序列化相结合?

我的问题的一个好的和可扩展的解决方案将是伟大的,因为XML结构是复杂的(相同的问题多次与不同的内容等)。 我不能改变XML的结构,所以请不要指出它。

途径

IXmlSerializable的

我尝试在DeactivationsGroup类上实现IXmlSerializable接口,以便使用这些名称搜索XmlElements的给定语言列表,并反序列化这些XmlElements的内容。

但是这种方法没有用,因为你必须手动映射所有属性。

IExtensibleDataObject

接口仅由DataContractSerializer支持。在最坏的情况下,如果没有找到其他解决方案,我可以在反序列化后使用此接口进行反序列化。

OnDeserialization

XmlSerializer不支持此属性,但会提供我可能需要的功能。

XmlAnyElement将

我想这是目前最好的选择。反序列化完成后是否存在一些回调以使其自动化?

可执行代码

到目前为止,这是整个代码。

public void Parse()
{
    string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + 
"    <Objects>" + 
"       <Group index=\"1\">" + 
"           <de>" + 
"               <GroupName>ANTRIEB</GroupName>" + 
"           </de>" + 
"           <en>" + 
"               <GroupName>missing translation!</GroupName>" + 
"           </en>" + 
"           <Level>2</Level>" + 
"       </Group>" + 
"       <Group index=\"2\">" + 
"           <de>" + 
"               <GroupName>BREMSEN</GroupName>" + 
"           </de>" + 
"           <Level>3</Level>" + 
"       </Group>" + 
"    </Objects>";

    XmlSerializer serializer = new XmlSerializer(typeof(DeactivationsXml));

    using (TextReader fileStream = new StringReader(xml))
    {
        var result = (DeactivationsXml)serializer.Deserialize(fileStream);
    }
}

[XmlRoot("Objects")]
public class DeactivationsXml
{
    [XmlElement("Group")]
    public DeactivationsGroup[] Groups { get; set; }
}

[Serializable()]
public class DeactivationsGroup
{
    [XmlIgnore]
    public Dictionary<string, GroupName> GroupNames { get; set; } = new Dictionary<string, GroupName>();

    public int Level { get; set; }

    [XmlAttribute]
    public byte index { get; set; }
}

public class GroupName
{
    [XmlElement("GroupName")]
    public string Name { get; set; }
}

3 个答案:

答案 0 :(得分:2)

您可以采用this answer中的方法并添加标有[XmlAnyElement]的代理XmlElement []属性,该属性对{的键/值对执行嵌套(反)序列化{1}}属性,将字典键绑定到元素名称。

请注意,虽然Dictionary<string, GroupName>状态的documentation

  

指定成员(返回XmlElement或XmlNode对象数组的字段)包含表示在被序列化或反序列化的对象中没有相应成员的任何XML元素的对象。

实际上,该属性也可以应用于属性。因此,不需要(反)序列化回调,因为嵌套序列化可以在代理属性本身的getter和setter内执行。如果您更喜欢新的LINQ-to-XML API,它也可以应用于返回XmlAnyElementAttribute个对象而不是XElement的成员。

在这种方法中,您的XmlElement看起来像是:

DeactivationsGroup

使用以下扩展方法和类:

[Serializable()]
public class DeactivationsGroup
{
    public DeactivationsGroup() { this.GroupNames = new Dictionary<string, GroupName>(); }

    [XmlIgnore]
    public Dictionary<string, GroupName> GroupNames { get; set; }

    public int Level { get; set; }

    [XmlAttribute]
    public byte index { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement[] XmlGroupNames
    {
        get
        {
            return GroupNames.SerializeToXElements(null);
        }
        set
        {
            if (value == null || value.Length < 1)
                return;
            foreach (var pair in value.DeserializeFromXElements<GroupName>())
            {
                GroupNames.Add(pair.Key, pair.Value);
            }
        }
    }
}

示例fiddleanother演示了XML命名空间和属性的案例。

答案 1 :(得分:0)

尝试使用xml linq

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);
            DeactivationsGroup.GroupNames = doc.Descendants("Group").Select(x => new {
                languages = x.Elements().Where(y => y.Element("GroupName") != null).Select(y => new DeactivationsGroup() { 
                   name = (string)y.Element("GroupName"),
                   level = (int)x.Element("Level"),
                   index = byte.Parse((string)x.Attribute("index")),
                   language = y.Name.LocalName
                })
            }).SelectMany(y => y.languages)
            .GroupBy(x => x.name, y => y)
            .ToDictionary(x => x.Key, y => y.FirstOrDefault());

        }
        public class DeactivationsGroup
        {
            public static Dictionary<string, DeactivationsGroup> GroupNames { get; set; }

            public string name { get; set; }
            public int level { get; set; }

            public byte index { get; set; }

            public string language { get; set; }
        }

    }
}

答案 2 :(得分:0)

这很容易完成如下。

使用你的一组课程。

在序列化程序上设置事件处理程序:

var serializer = new XmlSerializer(typeof(DeactivationsXml));
serializer.UnknownElement += Serializer_UnknownElement;

此处理程序中的代码非常简单:

private void Serializer_UnknownElement(object sender, XmlElementEventArgs e)
{
    var group = (DeactivationsGroup)e.ObjectBeingDeserialized;
    group.GroupNames.Add(e.Element.Name, new GroupName { Name = e.Element.InnerText });
}

Fiddle.