【问题标题】:Deserialize xml into a class with different hierarchy?将xml反序列化为具有不同层次结构的类?
【发布时间】:2017-10-09 19:38:28
【问题描述】:

这会将 xml 样本反序列化为“XmlModel”类。

using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace XmlTest
{
    public class DeserializeXml
    {
        public XmlModel GetXmlModel()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
            <root>
                <foo>
                    <bar>1</bar>
                    <bar>2</bar>
                </foo>
            </root>";

            var dS = new XmlSerializer(typeof(XmlModel));

            var m = new XmlModel();
            using (var reader = new StringReader(xml))
            {
                return (XmlModel) dS.Deserialize(reader);
            }
        }
    }

    [XmlRoot("root")]
    public class XmlModel
    {
        [XmlArray("foo")]
        [XmlArrayItem("bar")]
        public List<string> Foo { get; set; }
    }
}

这将得到模型:

var d = new DeserializeXml();
result = d.GetXmlModel();

我正在使用遗留代码,除了更改 XmlAttributes 之外,我无法更改 XmlModel 类。这是问题所在:实际的 Xml 没有“foo”节点:

string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<root>
    <bar>1</bar>
    <bar>2</bar>
</root>";

所以现在我的任务是让反序列化器吞下这个 xml 并输出类型 XmlModel。如果没有 Xslt 预处理或其他更复杂的方法,这可能吗?

【问题讨论】:

  • 您可以反序列化为与 XML 匹配的格式,然后使用 AutoMapper 之类的内容“升级”它。不是超级性能,但我不知道您在该领域的要求是什么,它可能是可以接受的。
  • 您还受到哪些其他限制?你可以使用不同的反序列化方法吗?
  • @Bradley Uffner 使用代理类 + 自动映射器是一种选择 - 但我认为会涉及一些努力 + 性能损失(比 XSLT prrprocessor 更多)。如果单纯的 XmlAttribute 技巧行不通(我希望他们能行),另一种序列化程序是我没有考虑过的一个选项,并且可能是一个解决方案。
  • XmlSerializerAttributes 可能有一些内置方法可以做到这一点。这是 .NET 的一个领域,我还没有真正探索过,所以我不确定。我只是想找出适合您的选项。
  • 遗留代码可能一成不变,但 Xml 肯定不是。您是否考虑过在尝试反序列化之前临时修改 Xml(添加 Foo 根节点)?否则,在我看来,您将需要一个自定义反序列化器。

标签: c# xml xmlserializer


【解决方案1】:

您可以使用XmlAttributeOverrides 为您的XmlModel 指定备用XML 属性,然后通过执行以下操作使用construct an XmlSerializer 使用这些属性:

var serializer = new XmlSerializer(typeof(XmlModel), overrides).

但是,请注意来自documentation 的以下警告:

为了提高性能,XML 序列化基础结构动态生成程序集以序列化和反序列化指定类型。基础结构查找并重用这些程序集。此行为仅在使用以下构造函数时发生:

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(Type, String)

如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中...

以下静态类创建并缓存 2 个序列化程序,一个用于 XmlModel 的“当前”版本,另一个用于 &lt;bar&gt; 元素缺少外部容器元素的“替代”版本:

public static class XmlModelSerializer<TRoot>
{
    static XmlSerializer alternateSerializerInstance;
    static XmlSerializer currentSerializerInstance;

    public static XmlSerializer AlternateSerializerInstance { get { return alternateSerializerInstance; } }

    public static XmlSerializer CurrentSerializerInstance { get { return currentSerializerInstance; } }

    static XmlModelSerializer()
    {
        XmlAttributes alternateAttributes = new XmlAttributes
        {
            XmlElements = { new XmlElementAttribute("bar") },
        };
        XmlAttributeOverrides alternateOverrides = new XmlAttributeOverrides();
        alternateOverrides.Add(typeof(XmlModel), "Foo", alternateAttributes);
        alternateSerializerInstance = new XmlSerializer(typeof(TRoot), alternateOverrides);

        XmlAttributes currentAttributes = new XmlAttributes
        {
            XmlArray = new XmlArrayAttribute("foo"),
            XmlArrayItems = { new XmlArrayItemAttribute("bar") },
        };
        XmlAttributeOverrides currentOverrides = new XmlAttributeOverrides();
        currentOverrides.Add(typeof(XmlModel), "Foo", currentAttributes);
        currentSerializerInstance = new XmlSerializer(typeof(TRoot), currentOverrides);
    }
}

通过使用两种不同的序列化程序,一种用于每种可能的 XML 格式,您可以避免对旧的 XmlModel 类型进行任何更改。

然后,反序列化表单的扁平化 XML

<root>
    <bar>1</bar>
    <bar>2</bar>
</root>

您只需这样做:

var dS = XmlModelSerializer<XmlModel>.AlternateSerializerInstance;
using (var reader = new StringReader(xml))
{
    return (XmlModel) dS.Deserialize(reader);
}

示例fiddle 显示了两种格式的反序列化。

【讨论】:

  • 这不仅表明在 public List Foo { get; 之上使用 [XmlElement("bar")]放; } 将省略 "foo" 节点,它还显示了如何在两个反序列化之间动态切换 - 大。
【解决方案2】:

如果您愿意使用另一种反序列化方法,这将起作用。它应该与XmlSerializer 一样快,甚至更快。它只是在原始 xml 上打开一个 XmlReader,移动到第一个“数据”元素,将数据转储到一个列表中,然后填充并从中返回您的 XmlModel

LINQPad 文件可用here

public XmlModel GetXmlModel()
{
    string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
        <root>
                <bar>1</bar>
                <bar>2</bar>
        </root>";
    using (var reader = XmlReader.Create(new StringReader(xml)))
    {
        reader.MoveToContent();
        var data = new List<string>();
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                var element = XNode.ReadFrom(reader) as XElement;
                switch (element.Name.LocalName)
                {
                    case "bar":
                        {
                            data.Add(element.Value);
                            break;
                        }
                }
            }
        }
        return new XmlModel() { Foo = data };
    }
}

如果您的 bar 类不仅仅是一个简单的内在类型,例如 string,这显然会变得更加复杂。

【讨论】:

  • 您的答案看起来好像是改编自msdn.microsoft.com/en-us/library/…,但不幸的是,MSDN 代码有一个错误——当 XML 没有缩进时它会跳过元素。请参阅this answer 进行分析。就我个人而言,我建议只加载到 XElement 并在内存中做所有事情,正是因为直接使用 XmlReader 非常麻烦。
  • 我承认,我确实看过该示例以供参考。你的建议也是我的首选方式,但我试图让事情尽可能快,因为 op 在讨论中提到了性能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-01
  • 2021-05-06
  • 2016-05-27
相关资源
最近更新 更多