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

Mik*_*ike 27 c# xml serialization xml-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>
Run Code Online (Sandbox Code Playgroud)

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; }
}
Run Code Online (Sandbox Code Playgroud)

其中Cars对象上的SalesPerson属性将包含一个流,该流具有在通过XmlSerializer运行后在<SalesPerson> xml元素内的原始xml.

可以这样做吗?我可以选择仅反序列化xml文档的"部分"吗?

谢谢!-麦克风

ps示例xml从如何反序列化XML文档中被盗

小智 39

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

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

reader.ReadToDescendant("InnerObjectTag"); //tag which matches the InnerObject
Run Code Online (Sandbox Code Playgroud)

然后为我们想要反序列化和反序列化的对象类型创建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 
Run Code Online (Sandbox Code Playgroud)

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


Sti*_*son 7

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

...xmlns=''> was not expected
Run Code Online (Sandbox Code Playgroud)

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

<?xml version=""1.0"" encoding=""UTF-8""?>
<Api>
  <Authentication>                       
      <sessionid>xxx</sessionid>
      <errormessage>xxx</errormessage>                
  </Authentication>
</ApI>
Run Code Online (Sandbox Code Playgroud)

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

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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

    /// <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);
    }
}
Run Code Online (Sandbox Code Playgroud)