将 XmlDocument 的一部分反序列化为对象 [英] Deserialize portion of XmlDocument into object

查看:27
本文介绍了将 XmlDocument 的一部分反序列化为对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常看到这个问题,但似乎没有人的标题真正描述了他们的问题.我从包含一般响应信息的 Web API 返回一个大型响应对象,以及我想要反序列化的数据对象.

I see this question often enough, but nobody's title really seems to depict their question. I get a large response object back from a Web API that contains general response information, along with the data object I want to deserialize.

完整的 XML:

<?xml version="1.0"?>
<root>
  <status>
      <apiErrorCode>0</apiErrorCode>
      <apiErrorMessage/>
      <dbErrorCode>0</dbErrorCode>
      <dbErrorMessage/>
      <dbErrorList/>
  </status>
<data>
    <modelName>ReportXDTO</modelName>
    <modelData>
        <id>1780</id>
        <reportTitle>Access Level (select) with Door Assignment</reportTitle>
        <hasParameters>true</hasParameters>
        <parameters>
            <dataType>STRING</dataType>
            <title>Access Level:</title>
            <index>1</index>
            <allowMulti>true</allowMulti>
            <selectSql>SELECT DISTINCT [Name] FROM dbo.[Levels] WHERE [PrecisionFlag] = '0' ORDER BY [Name] </selectSql>
            <values>
                <value>Door 1</value>
                <used>1</used>
            </values>
            <values>
                <value>Door 2</value>
                <used>1</used>
            </values>
            <values>
                <value>Door 3</value>
                <used>1</used>
            </values>
       </parameters>
       <sourceSql>SELECT [Name], [SData] FROM [Schedules]</sourceSql>
       <report/>
   </modelData>
   <itemReturned>1</itemReturned>
   <itemTotal>1</itemTotal>
</data>
<listInfo>
    <pageIdRequested>1</pageIdRequested>
    <pageIdCurrent>1</pageIdCurrent>
    <pageIdFirst>1</pageIdFirst>
    <pageIdPrev>1</pageIdPrev>
    <pageIdNext>1</pageIdNext>
    <pageIdLast>1</pageIdLast>
    <itemRequested>1</itemRequested>
    <itemReturned>1</itemReturned>
    <itemStart>1</itemStart>
    <itemEnd>1</itemEnd>
    <itemTotal>1</itemTotal>
</listInfo>
</root>

我只想反序列化 modelData 元素.modelData 对象类型是动态的,具体取决于 API 调用.

I only want to deserialize the modelData element. The modelData object type is dynamic, depending on the API call.

我在其他应用中反序列化了xml,创建了如下方法,但不知道具体如何只获取modelData元素:

I deserialize xml in other applications, and created the following method, but don't know how to specifically ONLY get the modelData element:

    public static T ConvertXmltoClass<T>(HttpResponseMessage http, string elementName) where T : new()
    {
        var newClass = new T();

        try
        {
            var doc = JsonConvert.DeserializeXmlNode(http.Content.ReadAsStringAsync().Result, "root");

            XmlReader reader = new XmlNodeReader(doc);
            reader.ReadToFollowing(elementName);

            //The xml needs to show the proper object name
            var xml = reader.ReadOuterXml().Replace(elementName, newClass.GetType().Name);

            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                var serializer = new XmlSerializer(typeof(T));
                newClass = (T)serializer.Deserialize(stream);
            }
        }
        catch (Exception e)
        {
            AppLog.LogException(System.Reflection.MethodBase.GetCurrentMethod().Name, e);
        }

        return newClass;
    }

我已经多次更新此线程,以保持最新状态.我开始用第一个解决方案更新它.但该解决方案本身并没有解决问题.使用现在的代码,我没有任何异常,但不要将 xml 反序列化为我的对象.相反,我得到了一个新的空白对象.想法?

I have updated this thread multiple times now, to stay current. I started updating it with the first solution. But that solution by itself didn't solve the problem. With the code how it is right now, I get no exceptions, but don't get the xml deserialized to my object. Instead I get a new, blank object. Thoughts?

虽然对象类型可以改变,但这是我当前正在处理的对象:(请注意,我反序列化了 Web API 中 modelData 中的确切 xml)

THOUGH the object type can change, here is my current object I am dealing with: (PLEASE NOTE, that I deserialize the exact xml in modelData, in the Web API)

namespace WebApiCommon.DataObjects
{
    [Serializable]
    public class ReportXDto
    {
        public ReportXDto()
        {
            Parameters = new List<ReportParameterXDto>();
        }

        public int Id { get; set; }
        public string ReportTitle { get; set; }
        public bool HasParameters { get; set; } = false;
        public List<ReportParameterXDto> Parameters { get; set; }
        public string SourceSql { get; set; }
        public DataTable Report { get; set; }
    }

    [Serializable]
    public class ReportXDto
    {
        public ReportXDto()
        {
            Parameters = new List<ReportParameterXDto>();
        }

        public int Id { get; set; }
        public string ReportTitle { get; set; }
        public bool HasParameters { get; set; } = false;
        public List<ReportParameterXDto> Parameters { get; set; }
        public string SourceSql { get; set; }
        public DataTable Report { get; set; }
    }

    [Serializable]
    public class ReportParameterValuesXDto
    {
        public string Value { get; set; } = "";
        public bool Used { get; set; } = false;
    }


}

推荐答案

首先,XmlSerializer 区分大小写.因此,您的属性名称需要与 XML 元素名称完全匹配——除非被 覆盖控制 XML 序列化的属性,例如 [XmlElement(ElementName)="id")].为了生成具有正确大小写的数据模型,我使用了 http://xmltocsharp.azurewebsites.net/ 结果在:

Firstly, XmlSerializer is case sensitive. Thus your property names need to match the XML element names exactly -- unless overridden with an attribute that controls XML serialization such as [XmlElement(ElementName="id")]. To generate a data model with the correct casing I used http://xmltocsharp.azurewebsites.net/ which resulted in:

public class ReportParameterValuesXDto 
{
    [XmlElement(ElementName="value")]
    public string Value { get; set; }
    [XmlElement(ElementName="used")]
    public string Used { get; set; }
}

public class ReportParametersXDto 
{
    [XmlElement(ElementName="dataType")]
    public string DataType { get; set; }
    [XmlElement(ElementName="title")]
    public string Title { get; set; }
    [XmlElement(ElementName="index")]
    public string Index { get; set; }
    [XmlElement(ElementName="allowMulti")]
    public string AllowMulti { get; set; }
    [XmlElement(ElementName="selectSql")]
    public string SelectSql { get; set; }
    [XmlElement(ElementName="values")]
    public List<ReportParameterValuesXDto> Values { get; set; }
}

public class ReportXDto 
{
    [XmlElement(ElementName="id")]
    public string Id { get; set; }
    [XmlElement(ElementName="reportTitle")]
    public string ReportTitle { get; set; }
    [XmlElement(ElementName="hasParameters")]
    public string HasParameters { get; set; }
    [XmlElement(ElementName="parameters")]
    public ReportParametersXDto Parameters { get; set; }
    [XmlElement(ElementName="sourceSql")]
    public string SourceSql { get; set; }
    [XmlElement(ElementName="report")]
    public string Report { get; set; }
}

(生成模型后,我修改了类名以匹配您的命名约定.)

(After generating the model, I modified the class names to match your naming convention.)

给定正确的数据模型,您可以使用 XmlNodeReader如何反序列化使用 XmlSerializer 的大型文档中的节点,而无需重新序列化为中间 XML 字符串.以下扩展方法可以解决问题:

Given the correct data model, you can deserialize directly from a selected XmlNode using an XmlNodeReader as shown in How to deserialize a node in a large document using XmlSerializer without having to re-serialize to an intermediate XML string. The following extension method does the trick:

public static partial class XmlExtensions
{
    public static IEnumerable<T> DeserializeElements<T>(this XmlNode root, string localName, string namespaceUri)
    {
        return new XmlNodeReader(root).DeserializeElements<T>(localName, namespaceUri);
    }

    public static IEnumerable<T> DeserializeElements<T>(this XmlReader reader, string localName, string namespaceUri)
    {
        var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceUri);
        while (!reader.EOF)
        {
            if (!(reader.NodeType == XmlNodeType.Element && reader.LocalName == localName && reader.NamespaceURI == namespaceUri))
                reader.ReadToFollowing(localName, namespaceUri);

            if (!reader.EOF)
            {
                yield return (T)serializer.Deserialize(reader);
                // Note that the serializer will advance the reader past the end of the node
            }               
        }
    }
}

public static class XmlSerializerFactory
{
    // To avoid a memory leak the serializer must be cached.
    // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
    // This factory taken from 
    // https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648

    readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
    readonly static object padlock;

    static XmlSerializerFactory()
    {
        padlock = new object();
        cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
    }

    public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
    {
        if (serializedType == null)
            throw new ArgumentNullException();
        if (rootName == null && rootNamespace == null)
            return new XmlSerializer(serializedType);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(serializedType, rootName, rootNamespace);
            if (!cache.TryGetValue(key, out serializer))
                cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
            return serializer;
        }
    }
}

然后你会反序列化如下:

Then you would deserialize as follows:

var modelData = doc.DeserializeElements<ReportXDto>("modelData", "").FirstOrDefault();

工作示例 .Net fiddle 此处.

Working sample .Net fiddle here.

这篇关于将 XmlDocument 的一部分反序列化为对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆