2008-12-15 41 views
23

Aquí hay un ejemplo ficticio del problema que intento resolver. Si estoy trabajando en C#, XML y tienen la siguiente manera:Cómo deserializar solo parte de un documento XML en C#

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

el XML dentro vendedor puede ser muy largo, megabytes de tamaño. Quiero deserializar la etiqueta, , pero no deserializar el elemento XML de SalesPerson, sino mantenerlo en formato sin formato "para más adelante".

Esencialmente, me gustaría poder utilizar esto como una representación de Objetos del XML.

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

donde la propiedad SalesPerson en el objeto Coches contendría una corriente con el XML sin procesar que se encuentra dentro del SalesPerson > elemento XML < después de haber sido dirigido a través de un XmlSerializer.

se puede hacer esto? ¿Puedo elegir deserializar solo "parte de" un documento xml?

Gracias! -Mike

p.s. ejemplo xml robado de How to Deserialize XML document

Respuesta

30

Podría ser una poco hilo viejo, pero voy a publicar de todos modos. tuve el mismo problema (necesité deserializar como 10kb de datos de un archivo que tenía más de 1MB). En el objeto principal (que tiene un InnerObject que debe ser deserializador) implementé una interfaz IXmlSerializable, luego cambié el método ReadXml.

Tenemos XMLTextReader como entrada, la primera línea es leer hasta que una etiqueta XML:

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

A continuación, cree XMLSerializer para un tipo de objeto que queremos deserializar y deserializar que

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 

esto me ahorró mucho tiempo para deserializar y me permite leer solo una parte de XML (solo algunos detalles que describen el archivo, que pueden ayudar al usuario a decidir si el archivo es lo que quiere cargar).

+0

Gran solución, sin embargo, encontré que también necesitaba establecer la raíz xml del fragmento para evitar una excepción con una excepción interna que decía ... xmlns = ''> no se esperaba. Agregué otra respuesta para mi solución, debido a los límites de longitud de los comentarios. –

3

Puede controlar cómo se realiza su serialización implementando la interfaz ISerializable en su clase. Tenga en cuenta que esto también implicará un constructor con la firma del método (SerializationInfoInfo, StreamingContext context) y seguro de que puede hacer lo que está pidiendo con eso.

Sin embargo, eche un vistazo de cerca si realmente necesita hacer esto con la transmisión porque si no tiene que usar el mecanismo de transmisión, será más fácil lograr lo mismo con Linq a XML, y será más sencillo mantener a largo plazo (IMO)

1

Normalmente, la deserialización XML es una proposición de todo o nada, por lo que es probable que necesite personalizarla. Si no realiza una deserialización completa, corre el riesgo de que el xml esté mal formado dentro del elemento SalesPerson, por lo que el documento no es válido.

Si está dispuesto a aceptar ese riesgo, probablemente quiera hacer un análisis de texto básico para dividir los elementos de SalesPerson en un documento diferente utilizando recursos de procesamiento de texto plano, y luego procesar el XML.

Este es un buen ejemplo de por qué XML no siempre es la respuesta correcta.

2

Creo que el comentario anterior es correcto en su comentario de que XML podría no ser la mejor opción de una tienda de respaldo aquí.

Si tiene problemas de escala y no está aprovechando algunas de las otras sutilezas que obtiene con XML, como las transformaciones, sería mejor que utilice una base de datos para sus datos. Las operaciones que estás haciendo realmente parecen encajar más en ese modelo.

Sé que esto realmente no responde a su pregunta, pero pensé que destacaría una solución alternativa que podría utilizar. Una buena base de datos y un correlacionador OR apropiado como .netTiers, NHibernate o, más recientemente, LINQ to SQL/Entity Framework probablemente lo pondrán de nuevo en funcionamiento con cambios mínimos en el resto de su base de código.

+1

que está puede ser sólo un consumidor en una esb.so que no puede cambiar sus partes datastore.reading de XMLs es una legítima process.with un xmlreader de bajo nivel es posible indexar un archivo/transmitir y buscar/saltar directamente a cualquier posición en el documento. –

0

Es posible controlar qué partes de la clase Los coches son deserializan mediante la implementación del IXmlSerializable interfaz de la clase de los coches, y luego dentro de la ReadXml (XmlReader) método que le lea y deserializar los elementos del coche, pero cuando llegue el elemento SalesPerson leería su subárbol como una cadena y luego construiría un Stream sobre el contenido textual utilizando un StreamWriter.

Si nunca desea que XmlSerializer escriba el elemento SalesPerson, use el atributo [XmlIgnore]. No estoy seguro de lo que quiere que suceda cuando se serializa la clase Cars a su representación XML. ¿Está tratando de evitar solo la deserialización de SalesPerson mientras aún puede serializar la representación XML de SalesPerson representada por Stream?

Probablemente podría proporcionar un ejemplo de código de esto si desea una implementación concreta.

0

Si todo lo que quiere hacer es analizar el elemento SalesPerson pero mantenerlo como una cadena, debe usar Xsl Transform en lugar de "Deserialization". Si, por otro lado, desea analizar el elemento SalesPerson y solo rellenar un objeto en la memoria de todos los demás elementos no SalesPerson, Xsl Transform también podría ser el camino a seguir. Si los archivos son demasiado grandes, puede considerar separarlos y usar Xsl para combinar diferentes archivos xml para que SalesPerson I/O solo se produzca cuando lo necesite.

+0

El caso de uso es que los datos del coche que quiero como objetos para que mi programa pueda interactuar con él. El XML de SalesPerson simplemente se envía por cable a otro sistema, por lo que ni siquiera necesito inspeccionarlo. Básicamente, necesito obtener todos los datos, pero solo me importa lo que contengan los elementos del coche. – Mike

+0

Si ese es el caso, entonces todo lo que tiene que hacer es no suministrar XmlElementAttributes para serializar los datos que no son del automóvil. – devlord

+0

* deserialize, quiero decir – devlord

1

Intente definir la propiedad SalesPerson como tipo XmlElement. Esto funciona para los resultados de los servicios web ASMX, que utilizan la serialización XML. Yo pensaría que también funcionaría en la entrada. Esperaría que todo el elemento <SalesPerson> termine en el XmlElement.

+0

También pueden necesitar el XmlAnyAttribute en ese miembro. –

+0

¿Puedes decir por qué? –

+0

Puede que me equivoque, en realidad, ya que parece que XmlAny es para una propiedad que devuelve una * matriz * de XmlElements, no solo uno. –

0

Le sugiero que lea manualmente desde Xml, utilizando cualquier método liviano, como XmlReader, XPathDocument o LINQ-to-XML.

Cuando usted tiene que leer sólo 3 propiedades, supongo que puede escribir código que lee de forma manual desde ese nodo y tener un control completo de cómo se ejecuta en lugar de confiar en la serialización/deserialización

5

El aceptada answer de user271807 es una gran solución, pero me pareció, que también necesitaba establecer la raíz xml del fragmento de evitar una excepción con una excepción interna que dice algo como esto:

...xmlns=''> was not expected 

Este trown excepción fue cuando traté de deserializar sólo el elemento de autenticación interna de este documento xml:

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

así que terminé la creación de este método de extensión como una solución reutilizable - advertencia contiene am fuga de Emory, ver más abajo:

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

actualización 20ma de marzo de 2017 Como el comentario anterior señala, hay un problema de pérdida de memoria cuando se utiliza uno de los constructores de XmlSerializer, por lo que terminó usando una solución de almacenamiento en caché como se muestra a continuación:

/// <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); 
    } 
} 
+0

Estaba trabajando en un problema similar y encontré su pregunta y [esta publicación del blog] (https://blogs.msdn.microsoft.com/tess/2006/02/15/net-memory-leak-xmlserializing-your- way-to-a-memory-leak /) sobre una pérdida de memoria al usar el constructor XmlSerializer (Type, XmlRootAttribute). Necesitas verificar tu código. Creo que su método está creando un nuevo conjunto temporal cada vez que se lo llama. Probablemente deba realizar un almacenamiento en caché manual para cada combinación Type + innerStartTag. – plushpuffin

+0

Sí, gracias por recordarme. He actualizado mi respuesta con una solución. –

Cuestiones relacionadas