Cómo deserializar un documento XML

472

¿Cómo deserializo este documento 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>
  <Car>
    <StockNumber>1111</StockNumber>
    <Make>Honda</Make>
    <Model>Accord</Model>
  </Car>
</Cars>

Tengo esto:

[Serializable()]
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; }
}

.

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

}

.

public class CarSerializer
{
    public Cars Deserialize()
    {
        Cars[] cars = null;
        string path = HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data/") + "cars.xml";

        XmlSerializer serializer = new XmlSerializer(typeof(Cars[]));

        StreamReader reader = new StreamReader(path);
        reader.ReadToEnd();
        cars = (Cars[])serializer.Deserialize(reader);
        reader.Close();

        return cars;
    }
}

que no parecen funcionar :-(

Alex
fuente
Creo que necesita escapar de los corchetes angulares en su documento de muestra.
harpo
44
Esta respuesta es realmente muy buena: stackoverflow.com/a/19613934/196210
Anterior

Respuestas:

359

Aquí hay una versión que funciona. Cambié las XmlElementAttributeetiquetas a XmlElementporque en el xml los valores StockNumber, Make y Model son elementos, no atributos. También eliminé el reader.ReadToEnd();(esa función lee toda la secuencia y devuelve una cadena, por lo que la Deserialize()función ya no podía usar el lector ... la posición estaba al final de la secuencia). También me tomé algunas libertades con el nombramiento :).

Aquí están las clases:

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

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

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


[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
    [XmlArray("Cars")]
    [XmlArrayItem("Car", typeof(Car))]
    public Car[] Car { get; set; }
}

La función Deserializar:

CarCollection cars = null;
string path = "cars.xml";

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

StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();

Y el xml ligeramente modificado (necesitaba agregar un nuevo elemento para envolver <Cars> ... Net es exigente con la deserialización de matrices):

<?xml version="1.0" encoding="utf-8"?>
<CarCollection>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <Car>
    <StockNumber>1111</StockNumber>
    <Make>Honda</Make>
    <Model>Accord</Model>
  </Car>
</Cars>
</CarCollection>
Kevin Tighe
fuente
68
El [Serializable]es redundante si se usa XmlSerializer; XmlSerializersimplemente nunca verifica eso. Del mismo modo, la mayoría de los [Xml...]atributos son redundantes, ya que simplemente imitan el comportamiento predeterminado; es decir, de forma predeterminada, una propiedad llamada StockNumberse almacena como un elemento denominado <StockNumber>, sin necesidad de atributos para eso.
Marc Gravell
3
Tenga en cuenta que XmlElementAttribute = XmlElement (es una característica del lenguaje que puede omitir el sufijo "Attribute"). La solución real aquí es eliminar la llamada ReadToEnd () y agregar un nodo raíz. Pero mejor use el código de erymski que resuelve la pregunta (analice el xml dado)
Flamefire
2
Gracias Kevin, pero ¿y si elimino CarsCollection del XML de muestra? Eliminé Carscollection de las clases y el código Deserealize, pero no tuve éxito.
Vikrant
441

¿Qué tal si solo guarda el xml en un archivo y usa xsd para generar clases de C #?

  1. Escriba el archivo en el disco (lo llamé foo.xml)
  2. Genera el xsd: xsd foo.xml
  3. Genere el C #: xsd foo.xsd /classes

Et voila - y un archivo de código C # que debería poder leer los datos a través de XmlSerializer:

    XmlSerializer ser = new XmlSerializer(typeof(Cars));
    Cars cars;
    using (XmlReader reader = XmlReader.Create(path))
    {
        cars = (Cars) ser.Deserialize(reader);
    }

(incluya el foo.cs generado en el proyecto)

Marc Gravell
fuente
66
¡Tu eres el hombre! Gracias. para cualquier persona que lo necesite, "ruta" puede ser un flujo que cree a partir de una respuesta web de esta manera: var resp = response.Content.ReadAsByteArrayAsync (); var stream = nuevo MemoryStream (resp.Result);
Induster
1
Impresionante idea, pero no pude hacer que funcione correctamente para mi modelo un poco más complicado con lotes de matrices anidadas. Seguía recibiendo errores de conversión de tipo para las matrices anidadas, además el esquema de nombres generado dejaba algo que desear. Por lo tanto, terminé yendo por la ruta personalizada.
GotDibbs
99
Cómo llegar a xsd.exe
jwillmer
2
xsd.exe está disponible desde el símbolo del sistema de Visual Studio, no desde el símbolo del sistema de Windows. Vea si puede abrir el símbolo del sistema desde Visual Studio en Herramientas. Si no, intente acceder desde la carpeta de Visual Studio. Para VS 2012 se encuentra aquí: C: \ Archivos de programa (x86) \ Microsoft Visual Studio 12.0 \ Common7 \ Tools \ Shortcuts. En Windows 8 intente buscar "Visual Studio Tools".
goku_da_master
2
Para todos los que buscan XSD. Aquí hay un hilo SO: stackoverflow.com/questions/22975031/…
SOReader
229

Tienes dos posibilidades.

Método 1. Herramienta XSD


Suponga que tiene su archivo XML en esta ubicación C:\path\to\xml\file.xml

  1. Abra el símbolo del sistema del desarrollador
    Puede encontrarlo en Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools O si tiene Windows 8 puede comenzar a escribir el símbolo del sistema del desarrollador en la pantalla de inicio
  2. Cambie la ubicación a su directorio de archivos XML escribiendo cd /D "C:\path\to\xml"
  3. Cree un archivo XSD a partir de su archivo xml escribiendoxsd file.xml
  4. Cree clases de C # escribiendoxsd /c file.xsd

¡Y eso es! Ha generado clases de C # desde un archivo xml enC:\path\to\xml\file.cs

Método 2 - Pasta especial


Requiere Visual Studio 2012+

  1. Copie el contenido de su archivo XML al portapapeles
  2. Agregue a su solución un nuevo archivo de clase vacío ( Shift+ Alt+ C)
  3. Abra ese archivo y en el menú haga clic Edit > Paste special > Paste XML As Classes
    ingrese la descripción de la imagen aquí

¡Y eso es!

Uso


El uso es muy simple con esta clase auxiliar:

using System;
using System.IO;
using System.Web.Script.Serialization; // Add reference: System.Web.Extensions
using System.Xml;
using System.Xml.Serialization;

namespace Helpers
{
    internal static class ParseHelpers
    {
        private static JavaScriptSerializer json;
        private static JavaScriptSerializer JSON { get { return json ?? (json = new JavaScriptSerializer()); } }

        public static Stream ToStream(this string @this)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(@this);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }


        public static T ParseXML<T>(this string @this) where T : class
        {
            var reader = XmlReader.Create(@this.Trim().ToStream(), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document });
            return new XmlSerializer(typeof(T)).Deserialize(reader) as T;
        }

        public static T ParseJSON<T>(this string @this) where T : class
        {
            return JSON.Deserialize<T>(@this.Trim());
        }
    }
}

Todo lo que tienes que hacer ahora es:

    public class JSONRoot
    {
        public catalog catalog { get; set; }
    }
    // ...

    string xml = File.ReadAllText(@"D:\file.xml");
    var catalog1 = xml.ParseXML<catalog>();

    string json = File.ReadAllText(@"D:\file.json");
    var catalog2 = json.ParseJSON<JSONRoot>();
Damian Drygiel
fuente
16
+1 buena respuesta. Pero, el Paste XML As Classescomando apunta solo a .NET 4.5
ravy amiry
1
Esta es una excelente manera de generar el modelo si tiene instalado vs2012 +. Ejecuté la limpieza del código ReSharper después para usar las propiedades automáticas y luego hice algunas otras tareas de limpieza también. Puede generar a través de este método y luego copiar en un proyecto anterior si es necesario.
Scotty.NET
44
Apuntar a .net4.5 no es un problema. Simplemente inicie un proyecto temporal con dotnet4.5, copie y pegue allí y copie la fuente a su proyecto real.
LosManos
2
¿Dónde está el objeto o clase "catálogo"?
CB4
3
Para que "Pegar XML como clases" aparezca en ese menú en la comunidad VS 2017, debe haber instalado "ASP.NET y desarrollo web". Si falta, ejecute VS installer nuevamente para modificar su instalación.
Slion
89

El siguiente fragmento debería hacer el truco (y puede ignorar la mayoría de los atributos de serialización):

public class Car
{
  public string StockNumber { get; set; }
  public string Make { get; set; }
  public string Model { get; set; }
}

[XmlRootAttribute("Cars")]
public class CarCollection
{
  [XmlElement("Car")]
  public Car[] Cars { get; set; }
}

...

using (TextReader reader = new StreamReader(path))
{
  XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
  return (CarCollection) serializer.Deserialize(reader);
}

fuente
14
Esta es realmente la única respuesta. La respuesta aceptada tiene un par de fallas que pueden confundir a los principiantes.
Flamefire
24

Vea si esto ayuda:

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

.

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

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

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

Y si no se usa el programa xsd.exe que viene con Visual Studio para crear un documento de esquema basado en ese archivo xml, y luego volver a usarlo para crear una clase basada en el documento de esquema.

Joel Coehoorn
fuente
9

No creo que .net sea 'exigente con la deserialización de matrices'. El primer documento xml no está bien formado. No hay elemento raíz, aunque parece que lo hay. El documento canónico xml tiene una raíz y al menos 1 elemento (si es que lo tiene). En tu ejemplo:

<Root> <-- well, the root
  <Cars> <-- an element (not a root), it being an array
    <Car> <-- an element, it being an array item
    ...
    </Car>
  </Cars>
</Root>
janbak
fuente
7

pruebe este bloque de código si su archivo .xml se ha generado en algún lugar del disco y si ha utilizado List<T>:

//deserialization

XmlSerializer xmlser = new XmlSerializer(typeof(List<Item>));
StreamReader srdr = new StreamReader(@"C:\serialize.xml");
List<Item> p = (List<Item>)xmlser.Deserialize(srdr);
srdr.Close();`

Nota: C:\serialize.xmles la ruta de mi archivo .xml. Puede cambiarlo según sus necesidades.

sheetal nainwal
fuente
6

La respuesta de Kevin es buena, aparte del hecho de que, en el mundo real, a menudo no puede modificar el XML original para satisfacer sus necesidades.

También hay una solución simple para el XML original:

[XmlRoot("Cars")]
public class XmlData
{
    [XmlElement("Car")]
    public List<Car> Cars{ get; set; }
}

public class Car
{
    public string StockNumber { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
}

Y luego simplemente puede llamar:

var ser = new XmlSerializer(typeof(XmlData));
XmlData data = (XmlData)ser.Deserialize(XmlReader.Create(PathToCarsXml));
Kim Homann
fuente
¡Gracias! Su respuesta es exactamente lo que necesitaba, ya que no quería alterar gigabytes de archivos de registro.
Colin
Aunque vale la pena mencionar que la solución XmlSerializer es muy elegante, pero ciertamente no es muy rápida y reacciona de manera sensible a datos inesperados de Xml. Entonces, si su problema no requiere una deserialización completa, debería considerar usar solo la clase XmlReader más pragmática y con mejor rendimiento y recorrer los elementos <Car>.
Kim Homann
5

Pruebe esta clase genérica para la serialización y deserialización Xml.

public class SerializeConfig<T> where T : class
{
    public static void Serialize(string path, T type)
    {
        var serializer = new XmlSerializer(type.GetType());
        using (var writer = new FileStream(path, FileMode.Create))
        {
            serializer.Serialize(writer, type);
        }
    }

    public static T DeSerialize(string path)
    {
        T type;
        var serializer = new XmlSerializer(typeof(T));
        using (var reader = XmlReader.Create(path))
        {
            type = serializer.Deserialize(reader) as T;
        }
        return type;
    }
}
Hasan Javaid
fuente
4

Para principiantes

Encontré que las respuestas aquí fueron muy útiles, lo que decía que todavía luchaba (solo un poco) para que esto funcionara. Entonces, en caso de que ayude a alguien, explicaré la solución de trabajo:

XML de la pregunta original. El xml está en un archivo Class1.xml, pathse usa a en este archivo en el código para ubicar este archivo xml.

Utilicé la respuesta de @erymski para que esto funcionara, así que creé un archivo llamado Car.cs y agregué lo siguiente:

using System.Xml.Serialization;  // Added

public class Car
{
    public string StockNumber { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
}

[XmlRootAttribute("Cars")]
public class CarCollection
{
    [XmlElement("Car")]
    public Car[] Cars { get; set; }
}

El otro fragmento de código proporcionado por @erymski ...

using (TextReader reader = new StreamReader(path))
{
  XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
  return (CarCollection) serializer.Deserialize(reader);
}

... entra en su programa principal (Program.cs), static CarCollection XCar()así:

using System;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApp2
{
    class Program
    {

        public static void Main()
        {
            var c = new CarCollection();

            c = XCar();

            foreach (var k in c.Cars)
            {
                Console.WriteLine(k.Make + " " + k.Model + " " + k.StockNumber);
            }
            c = null;
            Console.ReadLine();

        }
        static CarCollection XCar()
        {
            using (TextReader reader = new StreamReader(@"C:\Users\SlowLearner\source\repos\ConsoleApp2\ConsoleApp2\Class1.xml"))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
                return (CarCollection)serializer.Deserialize(reader);
            }
        }
    }
}

Espero eso ayude :-)

Aprendiz lento
fuente
1
Funcionó para mi. Esta es una solución perfectamente funcional para la entrada xml dada (como en el ejemplo de OP) también. [XmlElement ("Car")] es el atributo correcto. En otros ejemplos, utilizaron XmlArray, etc., que no son necesarios mientras tengamos la propiedad definida como Público Car [] Cars {get; conjunto; } y lo deserializaría correctamente. Gracias.
Diwakar Padmaraja
3

¿Qué tal una clase genérica para deserializar un documento XML?

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generic class to load any xml into a class
// used like this ...
// YourClassTypeHere InfoList = LoadXMLFileIntoClass<YourClassTypeHere>(xmlFile);

using System.IO;
using System.Xml.Serialization;

public static T LoadXMLFileIntoClass<T>(string xmlFile)
{
    T returnThis;
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    if (!FileAndIO.FileExists(xmlFile))
    {
        Console.WriteLine("FileDoesNotExistError {0}", xmlFile);
    }
    returnThis = (T)serializer.Deserialize(new StreamReader(xmlFile));
    return (T)returnThis;
}

Esta parte puede o no ser necesaria. Abra el documento XML en Visual Studio, haga clic derecho en el XML, elija propiedades. Luego elija su archivo de esquema.

David C Fuchs
fuente
1
Esto me permitió reducir un poco el código de lógica de negocios y centralizar la funcionalidad en una clase auxiliar con todas las clases <T> que generé. Ya tenía el XML en una cadena, por lo que podría condensarlo a esto: `public static T LoadXMLFileIntoClass <T> (string xmlData)` {`XmlSerializer serializer = new XmlSerializer (typeof (T)); `serializador return (T). Deserialize (new StringReader (xmlData)); `} Gracias!
pwrgreg007
3

Un trazador de líneas:

var object = (Cars)new XmlSerializer(typeof(Cars)).Deserialize(new StringReader(xmlString));
Andre M
fuente
2

La idea es manejar todos los niveles para la deserialización. Vea una solución de muestra que resolvió mi problema similar

<?xml version="1.0" ?> 
 <TRANSACTION_RESPONSE>
    <TRANSACTION>
        <TRANSACTION_ID>25429</TRANSACTION_ID> 
        <MERCHANT_ACC_NO>02700701354375000964</MERCHANT_ACC_NO> 
        <TXN_STATUS>F</TXN_STATUS> 
        <TXN_SIGNATURE>a16af68d4c3e2280e44bd7c2c23f2af6cb1f0e5a28c266ea741608e72b1a5e4224da5b975909cc43c53b6c0f7f1bbf0820269caa3e350dd1812484edc499b279</TXN_SIGNATURE> 
        <TXN_SIGNATURE2>B1684258EA112C8B5BA51F73CDA9864D1BB98E04F5A78B67A3E539BEF96CCF4D16CFF6B9E04818B50E855E0783BB075309D112CA596BDC49F9738C4BF3AA1FB4</TXN_SIGNATURE2> 
        <TRAN_DATE>29-09-2015 07:36:59</TRAN_DATE> 
        <MERCHANT_TRANID>150929093703RUDZMX4</MERCHANT_TRANID> 
        <RESPONSE_CODE>9967</RESPONSE_CODE> 
        <RESPONSE_DESC>Bank rejected transaction!</RESPONSE_DESC> 
        <CUSTOMER_ID>RUDZMX</CUSTOMER_ID> 
        <AUTH_ID /> 
        <AUTH_DATE /> 
        <CAPTURE_DATE /> 
        <SALES_DATE /> 
        <VOID_REV_DATE /> 
        <REFUND_DATE /> 
        <REFUND_AMOUNT>0.00</REFUND_AMOUNT> 
    </TRANSACTION>
  </TRANSACTION_RESPONSE> 

El XML anterior se maneja en dos niveles

  [XmlType("TRANSACTION_RESPONSE")]
public class TransactionResponse
{
    [XmlElement("TRANSACTION")]
    public BankQueryResponse Response { get; set; }

}

El nivel interno

public class BankQueryResponse
{
    [XmlElement("TRANSACTION_ID")]
    public string TransactionId { get; set; }

    [XmlElement("MERCHANT_ACC_NO")]
    public string MerchantAccNo { get; set; }

    [XmlElement("TXN_SIGNATURE")]
    public string TxnSignature { get; set; }

    [XmlElement("TRAN_DATE")]
    public DateTime TranDate { get; set; }

    [XmlElement("TXN_STATUS")]
    public string TxnStatus { get; set; }


    [XmlElement("REFUND_DATE")]
    public DateTime RefundDate { get; set; }

    [XmlElement("RESPONSE_CODE")]
    public string ResponseCode { get; set; }


    [XmlElement("RESPONSE_DESC")]
    public string ResponseDesc { get; set; }

    [XmlAttribute("MERCHANT_TRANID")]
    public string MerchantTranId { get; set; }

}

De la misma manera que necesita un nivel múltiple con car as array Marque este ejemplo para la deserialización multinivel

makdu
fuente
1

Si obtiene errores al usar xsd.exe para crear su archivo xsd, use la clase XmlSchemaInference como se menciona en msdn . Aquí hay una prueba unitaria para demostrar:

using System.Xml;
using System.Xml.Schema;

[TestMethod]
public void GenerateXsdFromXmlTest()
{
    string folder = @"C:\mydir\mydata\xmlToCSharp";
    XmlReader reader = XmlReader.Create(folder + "\some_xml.xml");
    XmlSchemaSet schemaSet = new XmlSchemaSet();
    XmlSchemaInference schema = new XmlSchemaInference();

    schemaSet = schema.InferSchema(reader);


    foreach (XmlSchema s in schemaSet.Schemas())
    {
        XmlWriter xsdFile = new XmlTextWriter(folder + "\some_xsd.xsd", System.Text.Encoding.UTF8);
        s.Write(xsdFile);
        xsdFile.Close();
    }
}

// now from the visual studio command line type: xsd some_xsd.xsd /classes
goku_da_master
fuente
1

Simplemente puede cambiar un atributo para su propiedad de automóvil Cars de XmlArrayItem a XmlElment. Es decir, de

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

a

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlElement("Car")]
    public Car[] Car { get; set; }
}
XU Weijiang
fuente
1

Mi solución:

  1. Use Edit > Past Special > Paste XML As Classespara obtener la clase en su código
  2. Pruebe algo como esto: cree una lista de esa clase ( List<class1>), luego use el XmlSerializerpara serializar esa lista en un xmlarchivo.
  3. Ahora solo reemplaza el cuerpo de ese archivo con tus datos e intenta deserializehacerlo.

Código:

StreamReader sr = new StreamReader(@"C:\Users\duongngh\Desktop\Newfolder\abc.txt");
XmlSerializer xml = new XmlSerializer(typeof(Class1[]));
var a = xml.Deserialize(sr);
sr.Close();

NOTA: debe prestar atención al nombre raíz, no lo cambie. El mío es "ArrayOfClass1"

haiduong87
fuente
1
async public static Task<JObject> XMLtoNETAsync(XmlDocument ToConvert)
{
    //Van XML naar JSON
    string jsonText = await Task.Run(() => JsonConvert.SerializeXmlNode(ToConvert));

    //Van JSON naar .net object
    var o = await Task.Run(() => JObject.Parse(jsonText));

    return o;
}
Zoidbergseasharp
fuente
1
Ponga su respuesta siempre en contexto en lugar de simplemente pegar el código. Ver aquí para más detalles.
gehbiszumeis