草庐IT

c# - XMLSerializer 在反序列化派生类型时警告未知节点/属性

coder 2024-07-05 原文

我最近使用 XMLSerializer 为未知节点、元素和属性注册了事件处理程序,用于从类型层次结构中反序列化复杂类型。我这样做是因为我收到的一些 XML 来自第三方;我对数据格式更改感兴趣,这可能会给我带来麻烦。

在 XMLSerializer 生成的 XML 中,它使用标准 XML 属性 xsi:type="somederivedtypename" 来标识 XML 元素表示的实际派生类型。

我很惊讶地看到同一个序列化程序在反序列化时将它刚刚生成的相同属性视为未知。有趣的是,反序列化是正确和完整的(在我的真实程序中也有更复杂的类型和数据)。这意味着序列化程序会在反序列化的早期阶段正确评估类型信息。但是在稍后的数据提取阶段,该属性显然被误认为是对象的真实数据部分,这当然是未知的。

在我的应用程序中,无端警告最终会弄乱一个不受欢迎的通用日志文件。在我看来,序列化程序应该毫无问题地读回它生成的 XML。我的问题:

  • 我做错了什么吗?
  • 有解决方法吗?

这里是一个最小的例子:

using System;
using System.IO;
using System.Xml.Serialization;

namespace XsiTypeAnomaly
{
    /// <summary>
    /// A trivial base type.
    /// </summary>
    [XmlInclude(typeof(DerivedT))]
    public class BaseT{}

    /// <summary>
    /// A trivial derived type to demonstrate a serialization issue.
    /// </summary>
    public class DerivedT : BaseT
    {
        public int anInt { get; set; }
    }

    class Program
    {
        private static void serializer_UnknownAttribute
            (   object sender, 
                XmlAttributeEventArgs e )
        {
            Console.Error.WriteLine("Warning: Deserializing " 
                    + e.ObjectBeingDeserialized
                    + ": Unknown attribute "
                    + e.Attr.Name);
                }

        private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e)
        {
            Console.Error.WriteLine("Warning: Deserializing "
                    + e.ObjectBeingDeserialized
                    + ": Unknown node "
                    + e.Name);
        }

        private static void serializer_UnknownElement(object sender, XmlElementEventArgs e)
        {
            Console.Error.WriteLine("Warning: Deserializing "
                    + e.ObjectBeingDeserialized
                    + ": Unknown element "
                    + e.Element.Name);
        }

        /// <summary>
        /// Serialize, display the xml, and deserialize a trivial object.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            BaseT aTypeObj = new DerivedT() { anInt = 1 };
            using (MemoryStream stream = new MemoryStream())
            {
                var serializer = new XmlSerializer(typeof(BaseT));

                // register event handlers for unknown XML bits
                serializer.UnknownAttribute += serializer_UnknownAttribute;
                serializer.UnknownElement += serializer_UnknownElement;
                serializer.UnknownNode += serializer_UnknownNode;

                serializer.Serialize(stream, aTypeObj);
                stream.Flush();

                // output the xml
                stream.Position = 0;
                Console.Write((new StreamReader(stream)).ReadToEnd() + Environment.NewLine);
                stream.Position = 0;
                var serResult = serializer.Deserialize(stream) as DerivedT;

                Console.WriteLine(
                        (serResult.anInt == 1 ? "Successfully " : "Unsuccessfully ")
                    + "read back object");
            }
        }
    }
}

输出:

<?xml version="1.0"?>
<BaseT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="DerivedT">
  <anInt>1</anInt>
</BaseT>
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown node xsi:type
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown attribute xsi:type
Successfully read back object

最佳答案

Am I doing something wrong?

我不这么认为。我同意你的观点,即 XmlSerializer 应该在没有任何警告的情况下反序列化它自己的输出。此外,xsi:typeXML Schema specification 中定义的标准属性, 显然它受 XmlSerializer 支持,如您的示例所示并记录在 MSDN Library 中.

因此,这种行为看起来只是一种疏忽。我可以想象一组 Microsoft 开发人员在 .NET Framework 的开发过程中致力于 XmlSerializer 的不同方面,并且从未同时测试 xsi:type 和事件。

That means that the serializer evaluates the type information properly during an early stage in the deserialization. But during a later data-extraction stage the attribute is apparently mistaken for a true data part of the object, which is of course unknown.

你的观察是正确的。

XmlSerializer 类生成一个动态程序集来序列化和反序列化对象。在您的示例中,反序列化 DerivedT 实例的生成方法如下所示:

private DerivedT Read2_DerivedT(bool isNullable, bool checkType)
{
    // [Code that uses isNullable and checkType omitted...]

    DerivedT derivedT = new DerivedT();
    while (this.Reader.MoveToNextAttribute())
    {
        if (!this.IsXmlnsAttribute(this.Reader.Name))
            this.UnknownNode(derivedT);
    }

    this.Reader.MoveToElement();
    // [Code that reads child elements and populates derivedT.anInt omitted...]
    return derivedT;
}

反序列化器在读取 xsi:type 属性并决定创建 DerivedT 实例后调用此方法。如您所见,while 循环为除 xmlns 属性之外的所有属性引发 UnknownNode 事件。这就是您获得 xsi:type 的 UnknownNode(和 UnknownAttribute)事件的原因。

while 循环由内部 XmlSerializationReaderILGen.WriteAttributes 生成方法。代码相当复杂,但我看不到会导致 xsi:type 属性被跳过的代码路径(我在下面描述的第二种解决方法除外)。

Is there a workaround?

我会忽略 xsi:type 的 UnknownNode 和 UnknownAttribute 事件:

private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e)
{
    if (e.NodeType == XmlNodeType.Attribute &&
        e.NamespaceURI == XmlSchema.InstanceNamespace && e.LocalName == "type")
    {
        // Ignore xsi:type attributes.
    }
    else
    {
        // [Log it...]
    }
}

// [And similarly for UnknownAttribute using e.Attr instead of e...]

另一个(hackier,IMO)解决方法是将 xsi:type 映射到 BaseT 类中的虚拟属性:

[XmlInclude(typeof(DerivedT))]
public class BaseT
{
    [XmlAttribute("type", Namespace = XmlSchema.InstanceNamespace)]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] // Hide this useless property
    public string XmlSchemaType
    {
        get { return null; } // Must return null for XmlSerializer.Serialize to work
        set { }
    }
}

关于c# - XMLSerializer 在反序列化派生类型时警告未知节点/属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42342875/

有关c# - XMLSerializer 在反序列化派生类型时警告未知节点/属性的更多相关文章

  1. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  2. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  3. ruby - 在院子里用@param 标签警告 - 2

    我试图使用yard记录一些Ruby代码,尽管我所做的正是所描述的here或here#@param[Integer]thenumberoftrials(>=0)#@param[Float]successprobabilityineachtrialdefinitialize(n,p)#initialize...end虽然我仍然得到这个奇怪的错误@paramtaghasunknownparametername:the@paramtaghasunknownparametername:success然后生成的html看起来很奇怪。我称yard为:$yarddoc-mmarkdown我做错了什么?

  4. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

  5. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  6. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  7. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  8. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  9. ruby - Nokogiri 剥离所有属性 - 2

    我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog

  10. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

随机推荐