将 XML 文档反序列化为 C# class,具有多个根 (xmlns) 命名空间

Deserialize XML document to C# class, with multiple root (xmlns) namespaces

我正在尝试将具有不同根命名空间的 XML 文档反序列化为 C# class。

简而言之,我想像这样反序列化类似 xml 文档的多个版本:

<IndexRoot Code="0664" xmlns="http://tempuri/2012/1.0">
    <Name>Foo</Name>
    <Color>blue</Color>
    ...
</IndexRoot>


<IndexRoot Code="0678" xmlns="http://tempuri/2012/2.0">
    <Name>Bar</Name>
    <Character>Smurf</Character>
</IndexRoot>

每个版本下面显然可以有不同的元素,虽然大多数元素是相同的,但也有一些差异。在上面的示例中,名称属性在每个版本中都可用,而 Color/Character 对于每个版本都是唯一的。

理想情况下,我想将其抽象为一个简单的函数,它给我一个反映具体的 class。像这样:

public IndexRoot Get(string fileName) {
    var doc = XDocument.Load(fileName);
    return xmlSerializer.Deserialize<IndexRoot>(doc);   
}

在我当前的设置中,这失败了,因为需要在根元素上提供单个名称空间,反序列化器才能工作:

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/2.0")]
public class IndexRoot
{
    [XmlAttribute("Code")]
    public string Code { get; set; }

    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
}

如您所见,硬编码命名空间将适用于 2.0 版本,但对于其他版本将失败,但以下例外:"IndexRoot xmlns='http://tempuri/2012/1.0' was not expected."

问题:如何将 XML 反序列化为 C# 对象,同时考虑多个根名称空间?

理想情况下,这将反映到每个版本的具体类型中。但我什至会满足于获得具有共同的共享属性的 "base class"。无论哪种方式,我目前都坚持使用 [XmlRoot] 上的当前硬编码命名空间值。

我试过:

  1. 添加重复的 [XmlRoot] 属性(不支持)
  2. 创建一个基础 class (BaseIndexRoot),从中派生两个实例并用 [XmlRoot] 属性修饰这些派生对象(相同 "was not expected" 错误)
  3. 一起删除命名空间也会导致 "was not expected" 错误

我经常这样做

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


namespace ConsoleApplication91
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml = 
            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
            "<IndexRoot Code=\"0664\" xmlns=\"http://tempuri/2012/1.0\">" +
               "<Name>Foo</Name>" +
               "<Color>blue</Color>" +
           "</IndexRoot>";

            XDocument doc = XDocument.Parse(xml);
            XElement indexRoot = (XElement)doc.FirstNode;
            XNamespace ns = indexRoot.Name.Namespace;

            string name = indexRoot.Element(ns + "Name").Value;

            XElement indexRoot2 = doc.Descendants().Where(x => x.Name.LocalName == "IndexRoot").FirstOrDefault();

        }


    }

}

我能够通过以下构造解决 XML 具有相同结构但具有不同命名空间的文档的反序列化问题。

首先,我为每个特定版本创建了 derived classes 并用命名空间装饰了它们:

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/1.0")]
public class IndexRootV10 : IndexRoot { }

[Serializable, XmlRoot(ElementName = "IndexRoot", Namespace = "http://tempuri/2012/2.0")]
public class IndexRootV20 : IndexRoot { }

public class IndexRoot
{
    [XmlAttribute("Code")]
    public string Code { get; set; }

    [XmlElement(ElementName = "Code")]
    public string Code { get; set; }
}

之后我需要做的就是简单地修改反序列化函数以确定要应用哪个版本(派生 class):

public IndexRoot Get(string fileName) {
    var doc = XDocument.Load(fileName);

    switch (doc.Root?.Name.NamespaceName)
    {
        case "http://tempuri/2012/1.0":
            response = xmlSerializer.Deserialize<IndexRootV10>(doc);
            break;
        case "http://tempuri/2012/2.0":
            response = xmlSerializer.Deserialize<IndexRootV20>(doc);
            break;
        default:
            throw new NotSupportedException($"Unsupported xmlns namespace (version): {doc.Root.Name.NamespaceName}");
    }
}

尽管这是我最不满意的部分,但由于硬编码的 switch 语句,它确实可以正常工作。不知怎的,我忍不住想有更复杂的方法来解决这个问题。

从好的方面来说,如果特定版本具有不同的属性,派生的 classes 现在非常适合反映这一点。