如何将XmlNode[]反序列化为类型T

本文关键字:反序列化 类型 XmlNode | 更新日期: 2023-09-27 18:26:48

我们从SoapUI项目Xsd生成了这个类:

[System.CodeDom.Compiler.GeneratedCodeAttribute("MSBuild", "4.0.30319.18408")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://eviware.com/soapui/config")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://eviware.com/soapui/config", IsNullable=true)]
public partial class RestRequestStep : object, System.ComponentModel.INotifyPropertyChanged
{
    public RestRequest restRequest;
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string service;
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string resourcePath;
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string methodName;
}

项目文档包含一个名为config的xsd:anyType元素,该元素包含以下xml

<con:config service="api" resourcePath="xxx" methodName="GET" xsi:type="con:RestRequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <con:restRequest name="Request 1" mediaType="application/json">
        <con:settings>
            <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting>
        </con:settings>
        <con:endpoint>${#Project#CurrentEndpoint}</con:endpoint>
        <con:request/>
        <con:originalUri>http://localhost/</con:originalUri>
        <con:assertion type="Valid HTTP Status Codes" name="Valid HTTP Status Codes">
            <con:configuration>
                <codes>200</codes>
            </con:configuration>
        </con:assertion>
        <con:assertion type="Schema Compliance" name="Schema Compliance">
            <con:configuration>
                <definition/>
            </con:configuration>
        </con:assertion>
        <con:credentials>
            <con:authType>No Authorization</con:authType>
        </con:credentials>
        <con:jmsConfig JMSDeliveryMode="PERSISTENT"/>
        <con:jmsPropertyConfig/>
        <con:parameters>
            <entry key="connectionName" value="${#Project#ConnectionName}" xmlns="http://eviware.com/soapui/config"/>
        </con:parameters>
    </con:restRequest>
</con:config>

在该文档的包装器中,config属性的类型为object

在运行时,config的内容是包含config元素的所有子节点的XmlNode[]

我需要把XmlNode[]变成它应该是的类型,RestRequestStep

到目前为止,我得到了这个:

    public static T FromXml<T>(this IEnumerable<XmlNode> input)
    {
        T output;
        var type = typeof(T);
        var serializer = CreateSerializer(type);
        var doc = new XmlDocument();
        var rootAttribute = (XmlRootAttribute)type.GetCustomAttributes(true).Where(a => a is XmlRootAttribute).SingleOrDefault();
        string ns = null;
        if (rootAttribute != null)
        {
            ns = rootAttribute.Namespace;
        }
        doc.AppendChild(doc.CreateElement(type.Name, ns));
        foreach (var node in input)
        {
            var inode = doc.ImportNode(node, true);
            if (inode is XmlAttribute)
            {
                doc.DocumentElement.Attributes.Append((XmlAttribute)inode);
            }
            else
            {
                doc.DocumentElement.AppendChild(inode);
            }
        }
        output = (T)serializer.Deserialize(new StringReader(doc.OuterXml));
        return output;
    }

但在output = (T)serializer.Deserialize(new StringReader(doc.OuterXml));线上失败,出现以下异常:

System.InvalidOperationException was unhandled
  Message=There is an error in XML document (1, 2).
  Source=System.Xml
  InnerException: System.InvalidOperationException
       Message=Namespace prefix 'con' is not defined.
       Source=System.Xml

OuterXml的内容最终为:

<RestRequestStep service="api" resourcePath="xxx" methodName="GET" xsi:type="con:RestRequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://eviware.com/soapui/config">
    <con:restRequest name="Request 1" mediaType="application/json" xmlns:con="http://eviware.com/soapui/config">
        <con:settings>
            <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/&gt;</con:setting>
        </con:settings>
        <con:endpoint>${#Project#CurrentEndpoint}</con:endpoint>
        <con:request/>
        <con:originalUri>http://localhost/</con:originalUri>
        <con:assertion type="Valid HTTP Status Codes" name="Valid HTTP Status Codes">
            <con:configuration>
                <codes xmlns="">200</codes>
            </con:configuration>
        </con:assertion>
        <con:assertion type="Schema Compliance" name="Schema Compliance">
            <con:configuration>
                <definition xmlns=""/>
            </con:configuration>
        </con:assertion>
        <con:credentials>
            <con:authType>No Authorization</con:authType>
        </con:credentials>
        <con:jmsConfig JMSDeliveryMode="PERSISTENT"/>
        <con:jmsPropertyConfig/>
        <con:parameters>
            <entry key="connectionName" value="${#Project#ConnectionName}" xmlns="http://eviware.com/soapui/config"/>
        </con:parameters>
    </con:restRequest>
</RestRequestStep>

config的内容不应该是RestRequestStep而不是XmlNode[]吗?

我如何将XmlNode[]反序列化为类型T?

如何将XmlNode[]反序列化为类型T

找到了一个解决方案,看起来有点棘手。

xsi命名空间似乎有神奇的功能,即使con在"中的字符串中,它也会触发一些破坏反序列化程序的东西。

解决方案是忽略任何传入的xsi:type属性:

    public static T FromXml<T>(this IEnumerable<XmlNode> input)
    {
        T output;
        var type = typeof(T);
        XmlSerializer serializer = CreateSerializer(type);
        var doc = new XmlDocument();
        var rootAttribute = (XmlRootAttribute)type.GetCustomAttributes(true).Where(a => a is XmlRootAttribute).SingleOrDefault();
        string ns = null;
        if (rootAttribute != null)
        {
            ns = rootAttribute.Namespace;
        }
        doc.AppendChild(doc.CreateElement(type.Name, ns));
        foreach (var node in input)
        {
            if (node.Name != "type" && node.NamespaceURI != "http://www.w3.org/2001/XMLSchema-instance")
            {
                var inode = doc.ImportNode(node, true);
                if (inode is XmlAttribute)
                {
                    doc.DocumentElement.Attributes.Append((XmlAttribute)inode);
                }
                else
                {
                    doc.DocumentElement.AppendChild(inode);
                }
            }
        }
        output = (T)serializer.Deserialize(new StringReader(doc.OuterXml));
        return output;
    }

以下是"为什么它不反序列化为RestRequestStep

XmlSerializer构造函数的重载之一是(Type, Type[]),其中Type[]是"预期类型"的数组。

所以现在我有了:

    public static XmlSerializer CreateSerializer(Type incomingType, IEnumerable<Type> knownTypes = null)
    {
        XmlSerializer output;
        output = new XmlSerializer(incomingType, knownTypes.ToArray());
        return output;
    }
    public static T FromXml<T>(this string input, params Type[] knownTypes)
    {
        T output;
        var serializer = CreateSerializer(typeof(T), knownTypes);
        output = (T)serializer.Deserialize(new StringReader(input));
        return output;
    }
    var p = File.ReadAllText(@"testproject.xml").FromXml<Project>(typeof(RestRequestStep));