简体   繁体   中英

How can I deserialize an XmlNode[] into type T?

We have this class generated from the SoapUI project 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;
}

And the project document contains an xsd:anyType element called config which contains the following 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>

And in the wrapper for that document the config property is of type object

At runtime, the content of config is an XmlNode[] containing all the child nodes of the config element.

I need to turn that XmlNode[] into the type it should be, RestRequestStep

So far I've got this:

    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;
    }

But that fails on the line output = (T)serializer.Deserialize(new StringReader(doc.OuterXml)); with the following Exception:

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

The content of OuterXml ends up as:

<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>

And shouldn't the content of config be a RestRequestStep instead of an XmlNode[] ?

How do I deserialize an XmlNode[] into type T?

Got a solution, seems a bit hacky.

It seems there is magic functionality for the xsi namespace and even though con is inside a string in `` it triggers something which breaks the deserializer.

Solution was to ignore any incoming xsi:type attributes:

    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;
    }

Here's the solution to "Why doesn't it deserialize as a RestRequestStep ?

One of the overloads of the XmlSerializer constructors is (Type, Type[]) , where the Type[] is an array of "expected types".

So now I have:

    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));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM