【问题标题】:Deserialize portion of XmlDocument into object将 XmlDocument 的部分反序列化为对象
【发布时间】:2018-04-20 01:49:44
【问题描述】:

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

完整的 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 调用。

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

    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 反序列化为我的对象。相反,我得到了一个新的空白对象。想法?

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

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


}

【问题讨论】:

  • 我对你想做什么有点困惑。为什么要从 JSON 转换为 XML?为什么不直接从 JSON 反序列化?另外,为什么是XmlDocument 而不是XDocument
  • 我从中获取数据的 Web API 返回一个 json 响应。从 json 到 XmlDocument 的反序列化效果很好,我在许多其他应用程序中使用该方法。这个问题是独一无二的,因为 API 响应中返回的 modelData 是 XML。
  • 只要我可以将 modelData 反序列化为我的 T 对象,我就不再关心我必须实施什么解决方案。
  • 您可以使用来自Deserialize object property with StringReader vs XmlNodeReaderc#, XML How to cycle through an XML node fetched with XMLNode.SelectSingleNodeProperXmlNodeReader 直接从XmlNode 反序列化。或者,如果您切换到 LINQ to XML,您可以使用 XObjectExtensions.Deserialize&lt;T&gt;(this XContainer element) from Changing type of element in XML Serialization
  • 那么,现在到底是什么问题呢?是不是元素名称&lt;modelData&gt; 与您尝试反序列化的T 类型的根元素名称不匹配?您能否提供一个 minimal reproducible example,其中包含您尝试反序列化的类型 T

标签: c# xml serialization xmlserializer


【解决方案1】:

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

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

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

如果数据模型正确,您可以使用XmlNodeReader 直接从选定的XmlNode 反序列化,如How to deserialize a node in a large document using XmlSerializer 所示,无需重新序列化为中间XML 字符串。以下扩展方法可以解决问题:

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

然后你会反序列化如下:

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

工作示例 .Net fiddle here.

【讨论】:

    【解决方案2】:

    对于巨大的 xml 文件,请始终使用 XmlReader,这样您就不会遇到内存不足的问题。请参阅下面的代码以将元素作为字符串获取:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
                //or Create(Stream)
                XmlReader reader = XmlReader.Create(FILENAME);
    
                reader.ReadToFollowing("modelData");
                if (!reader.EOF)
                {
                    string modelDataStr = reader.ReadOuterXml();
                }
            }
        }
    }
    

    【讨论】:

    • OK....所以在我的情况下,我从 API 调用的响应中传递了 XML...。我只需要反序列化 modelData 元素中的元素,如何我能做到吗?
    • var reader = new XmlNodeReader(doc); reader.ReadToFollowing(elementName); reader.ReadOuterXml(); //这不会让我得到对象。下界没有 reader.ReadInnerXml()
    • 对象是否为空?您使用的是文件名还是流?我假设它来自 http.Content 但不确定您是要读取字符串还是流。我用发布的 xml 进行了测试,它得到了正确的元素,所以请确保你得到了正确的响应。
    • 我有从 JsonConvert.DeserializeXmlNode 创建的 XmlDocument。 XmlReader.Create(doc.InnerXml),它抛出一个异常
    • XmlReader reader = new XmlNodeReader(doc); reader.ReadToFollowing(elementName); reader.ReadOuterXml(); //这不会失败,直到我尝试反序列化流
    猜你喜欢
    • 1970-01-01
    • 2011-02-11
    • 2017-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 2021-01-19
    相关资源
    最近更新 更多