如何在C#中仅反序列化部分XML文档

时间:2008-12-15 21:36:24

标签: c# xml xml-serialization serialization

这是我试图解决的问题的一个虚构的例子。如果我在C#中工作,并且像这样使用XML:

<?xml version="1.0" encoding="utf-8"?>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <SalesPerson>
    <Company>Acme Sales</Company>
    <Position>
       <Salary>
          <Amount>1000</Amount>
          <Unit>Dollars</Unit>
    ... and on... and on....
  </SalesPerson>
</Cars>

SalesPerson中的XML可能非常长,大小为兆字节。我想反序列化标记,不反序列化SalesPerson XML元素,而是将其保留为原始格式“以便以后再使用”。

基本上我希望能够将它用作XML的Objects表示。

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }

    public Stream SalesPerson { get; set; }
}

public class Car
{
    [System.Xml.Serialization.XmlElementAttribute("StockNumber")]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Make")]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Model")]
    public string Model{ get; set; }
}

其中Cars对象上的SalesPerson属性将包含一个带有原始xml的流,该原始xml位于&lt; SalesPerson&gt;内。通过XmlSerializer运行后的xml元素。

可以这样做吗?我可以选择仅对xml文档的“部分”进行反序列化吗?

谢谢! -Mike

P.S。示例xml从How to Deserialize XML document

被盗

9 个答案:

答案 0 :(得分:36)

这可能是一个有点旧的线程,但无论如何我会发布。我有同样的问题(需要从一个超过1MB的文件反序列化10kb的数据)。在主对象(具有需要反序列化的InnerObject)中,我实现了一个IXmlSerializable接口,然后更改了ReadXml方法。

我们有xmlTextReader作为输入,第一行是读取XML标记:

reader.ReadToDescendant("InnerObjectTag"); //tag which matches the InnerObject

然后为我们想要反序列化和反序列化的对象类型创建XMLSerializer

XmlSerializer   serializer = new XmlSerializer(typeof(InnerObject));

this.innerObject = serializer.Deserialize(reader.ReadSubtree()); //this gives serializer the part of XML that is for  the innerObject data

reader.close(); //now skip the rest 

这为我节省了大量反序列化的时间,并允许我只读取XML的一部分(只是描述文件的一些细节,这可能有助于用户决定文件是否是他想要加载的内容)。

答案 1 :(得分:7)

来自user271807的已接受answer是一个很好的解决方案,但我发现,我还需要设置片段的xml根,以避免异常,内部异常如下所示:

...xmlns=''> was not expected

当我尝试仅反序列化此xml文档的内部Authentication元素时,会抛出此异常:

<?xml version=""1.0"" encoding=""UTF-8""?>
<Api>
  <Authentication>                       
      <sessionid>xxx</sessionid>
      <errormessage>xxx</errormessage>                
  </Authentication>
</ApI>

所以我最终创建了这个扩展方法作为可重用的解决方案 - 警告包含内存泄漏,见下文:

public static T DeserializeXml<T>(this string @this, string innerStartTag = null)
        {
            using (var stringReader = new StringReader(@this))
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute(innerStartTag));
                    return (T)xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T)new XmlSerializer(typeof(T)).Deserialize(xmlReader);
            }
        }

2017年3月20日更新:正如下面的评论所指出的,使用XmlSerializer的一个构造函数时存在内存泄漏问题,因此我最终使用了一个缓存解决方案,如下所示: < / p>

    /// <summary>
    ///     Deserialize XML string, optionally only an inner fragment of the XML, as specified by the innerStartTag parameter.
    /// </summary>
    public static T DeserializeXml<T>(this string @this, string innerStartTag = null) {
        using (var stringReader = new StringReader(@this)) {
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute(innerStartTag));
                    return (T) xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T) CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute("AutochartistAPI")).Deserialize(xmlReader);
            }
        }
    }
/// <summary>
///     A caching factory to avoid memory leaks in the XmlSerializer class.
/// See http://dotnetcodebox.blogspot.dk/2013/01/xmlserializer-class-may-result-in.html
/// </summary>
public static class CachingXmlSerializerFactory {
    private static readonly ConcurrentDictionary<string, XmlSerializer> Cache = new ConcurrentDictionary<string, XmlSerializer>();
    public static XmlSerializer Create(Type type, XmlRootAttribute root) {
        if (type == null) {
            throw new ArgumentNullException(nameof(type));
        }
        if (root == null) {
            throw new ArgumentNullException(nameof(root));
        }
        var key = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", type, root.ElementName);
        return Cache.GetOrAdd(key, _ => new XmlSerializer(type, root));
    }
    public static XmlSerializer Create<T>(XmlRootAttribute root) {
        return Create(typeof (T), root);
    }
    public static XmlSerializer Create<T>() {
        return Create(typeof (T));
    }
    public static XmlSerializer Create<T>(string defaultNamespace) {
        return Create(typeof (T), defaultNamespace);
    }
    public static XmlSerializer Create(Type type) {
        return new XmlSerializer(type);
    }
    public static XmlSerializer Create(Type type, string defaultNamespace) {
        return new XmlSerializer(type, defaultNamespace);
    }
}

答案 2 :(得分:3)

您可以通过在类中实现ISerializable接口来控制序列化的完成方式。请注意,这也意味着一个带有方法签名的构造函数(SerializationInfo info,StreamingContext context),并确保你可以用它来做你想要的。

然而,仔细研究一下你是否真的需要使用流式传输,因为如果你不必使用流式传输机制,那么使用Linq to XML实现同样的功能将更容易,并且更易于维护从长远来看(IMO)

答案 3 :(得分:2)

我认为之前的评论者在他的评论中是正确的,XML可能不是这里支持商店的最佳选择。

如果您遇到规模问题并且没有利用XML所带来的其他一些细节,比如转换,那么最好使用数据库来处理数据。您正在进行的操作似乎更适合该模型。

我知道这并没有真正回答你的问题,但我想我会强调你可能会使用的替代解决方案。一个好的数据库和一个适当的OR映射器,如.netTiers,NHibernate,或者最近的LINQ to SQL / Entity Framework,可能会让你备份并运行,只需对代码库的其余部分进行最小的更改。

答案 4 :(得分:1)

通常,XML反序列化是开箱即用的全有或全无的命题,因此您可能需要自定义。如果不进行完全反序列化,则存在在SalesPerson元素中xml格式错误的风险,因此文档无效。

如果您愿意接受这种风险,您可能希望进行一些基本的文本解析,使用纯文本处理工具将SalesPerson元素分解为不同的文档,然后处理XML。

这是为什么XML并不总是正确答案的一个很好的例子。

答案 5 :(得分:1)

请尝试将SalesPerson属性定义为XmlElement类型。这适用于使用XML序列化的ASMX Web服务的输出。我认为它也适用于输入。我希望整个<SalesPerson>元素能够在XmlElement中结束。

答案 6 :(得分:0)

您可以通过在Cars类上实现 IXmlSerializable 接口来控制Cars类的哪些部分反序列化,然后在您将阅读的 ReadXml(XmlReader)方法中并反序列化Car元素,但当您到达SalesPerson元素时,您将其子树读作字符串,然后使用StreamWriter在文本内容上构建Stream。

如果您不希望XmlSerializer写出SalesPerson元素,请使用[XmlIgnore]属性。当你将Cars类序列化为XML表示时,我不确定你想要发生什么。您是否尝试仅阻止SalesPerson的反序列化,同时仍能序列化Stream所代表的SalesPerson的XML表示形式?

如果你想要一个具体的实现,我可能会提供一个代码示例。

答案 7 :(得分:0)

如果您只想解析SalesPerson元素但将其保留为字符串,则应使用Xsl Transform而不是“Deserialization”。另一方面,如果要解析SalesPerson元素并仅从所有其他非SalesPerson元素填充内存中的对象,那么Xsl Transform也可能是最佳选择。如果文件很大,您可以考虑将它们分开并使用Xsl组合不同的xml文件,以便SalesPerson I / O仅在您需要时才会出现。

答案 8 :(得分:0)

我建议您使用XmlReader,XPathDocument或LINQ-to-XML等任何轻量级方法从Xml手动读取。

当你只需要阅读3个属性时,我想你可以编写从该节点手动读取的代码,并完全控制它的执行方式,而不是依赖于序列化/反序列化