Come deserializzare il documento XML


472

Come deserializzare questo 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>

Ho questo:

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

che sembra non funzionare :-(


Penso che devi sfuggire alle parentesi angolari nel tuo documento di esempio.
Harpo,

4
Questa risposta è veramente buono: stackoverflow.com/a/19613934/196210
sola esistenza di

Risposte:


359

Ecco una versione funzionante. Ho cambiato le XmlElementAttributeetichette in XmlElementperché nell'xml i valori StockNumber, Make e Model sono elementi, non attributi. Inoltre ho rimosso il reader.ReadToEnd();(quella funzione legge l'intero flusso e restituisce una stringa, quindi la Deserialize()funzione non poteva più usare il lettore ... la posizione era alla fine del flusso). Ho anche preso alcune libertà con la denominazione :).

Ecco le lezioni:

[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 funzione di deserializzazione:

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

E l'xml leggermente ottimizzato (avevo bisogno di aggiungere un nuovo elemento per avvolgere <Car> ... Net è schizzinoso riguardo alle matrici deserializzanti):

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

68
Il [Serializable]è ridondante se si utilizza XmlSerializer; XmlSerializersemplicemente non lo controlla mai. Allo stesso modo, la maggior parte degli [Xml...]attributi sono ridondanti, in quanto imita semplicemente il comportamento predefinito; ovvero, per impostazione predefinita, una proprietà chiamata StockNumberviene memorizzata come elemento denominato <StockNumber>- non sono necessari attributi per questo.
Marc Gravell

3
Si noti che XmlElementAttribute = XmlElement (è una funzione del linguaggio che è possibile omettere il suffisso "Attributo") La vera soluzione qui è la rimozione della chiamata ReadToEnd () e l'aggiunta di un nodo radice. Ma meglio usare il codice di erymski che risolve la domanda (analizza il dato xml)
Flamefire

2
Grazie Kevin, ma se rimuovessi CarsCollection dall'XML di esempio. Ho rimosso Carscollection dalle classi e deserealizzato il codice, ma non ci sono riuscito.
Vikrant,

441

Che ne dici di salvare l'xml in un file e usare xsd per generare classi C #?

  1. Scrivi il file su disco (l'ho chiamato foo.xml)
  2. Genera xsd: xsd foo.xml
  3. Genera il C #: xsd foo.xsd /classes

Et voilà - e file di codice C # che dovrebbe essere in grado di leggere i dati tramite XmlSerializer:

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

(includi il foo.cs generato nel progetto)


6
Tu sei l'uomo! Grazie. per chiunque ne abbia bisogno, "percorso" può essere uno Stream creato da una risposta Web in questo modo: var resp = response.Content.ReadAsByteArrayAsync (); var stream = new MemoryStream (resp.Result);
Induster

1
Un'idea fantastica, ma non riuscivo a farlo funzionare bene per il mio modello leggermente più complicato con lotti di array nidificati. Continuavo a ricevere errori di conversione del tipo per le matrici nidificate - inoltre lo schema di denominazione generato lasciava a desiderare. Pertanto ho finito per seguire il percorso personalizzato.
GotDibbs,

9
Come arrivare a xsd.exe
jwillmer

2
xsd.exe è disponibile dal prompt dei comandi di Visual Studio, non dal prompt dei comandi di Windows. Verifica se riesci ad aprire il prompt dei comandi da Visual Studio in Strumenti. In caso contrario, prova ad accedervi dalla cartella di Visual Studio. Per VS 2012 si trovava qui: C: \ Programmi (x86) \ Microsoft Visual Studio 12.0 \ Common7 \ Tools \ Shortcuts. In Windows 8 prova a cercare "Strumenti di Visual Studio".
goku_da_master,

2
Per tutti coloro che cercano XSD. Ecco un filo SO: stackoverflow.com/questions/22975031/...
SOReader

229

Hai due possibilità.

Metodo 1. Strumento XSD


Supponi di avere il tuo file XML in questa posizione C:\path\to\xml\file.xml

  1. Apri prompt dei comandi per sviluppatori
    Puoi trovarlo in Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools Oppure, se hai Windows 8, puoi semplicemente iniziare a digitare Prompt dei comandi per sviluppatori nella schermata Start
  2. Cambiare posizione nella directory del file XML digitando cd /D "C:\path\to\xml"
  3. Crea il file XSD dal tuo file xml digitandoxsd file.xml
  4. Crea classi C # digitandoxsd /c file.xsd

E questo è tutto! Hai generato classi C # dal file xml inC:\path\to\xml\file.cs

Metodo 2: incolla speciale


Richiesto Visual Studio 2012+

  1. Copia il contenuto del tuo file XML negli appunti
  2. Aggiungi alla tua soluzione un nuovo file di classe vuoto ( Shift+ Alt+ C)
  3. Apri quel file e nel menu fai clic Edit > Paste special > Paste XML As Classes
    inserisci qui la descrizione dell'immagine

E questo è tutto!

uso


L'utilizzo è molto semplice con questa classe di supporto:

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

Tutto quello che devi fare ora è:

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

16
+1 buona risposta. Ma il Paste XML As Classescomando ha come obiettivo solo .NET 4.5
ravy amiry del

1
Questo è un ottimo modo per generare il modello se hai installato vs2012 +. In seguito ho eseguito la pulizia del codice ReSharper per utilizzare le proprietà automatiche, quindi ho anche riordinato. È possibile generare tramite questo metodo e quindi copiarlo in un progetto più vecchio, se necessario.
Scotty.NET,

4
Targeting .net4.5 non è un problema. Basta avviare un progetto temporaneo con dotnet4.5, fare il copia-incolla lì e copiare l'origine nel progetto reale.
LosManos,

2
dov'è l'oggetto o la classe "catalog"?
CB4,

3
Affinché "Incolla XML come classi" venga visualizzato in quel menu su VS 2017 Community è necessario aver installato "ASP.NET e sviluppo web". Se manca, eseguire nuovamente il programma di installazione VS per modificare l'installazione.
Slion,

89

Il seguente frammento dovrebbe fare il trucco (e puoi ignorare la maggior parte degli attributi di serializzazione):

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

14
Questa è in realtà l'unica e sola risposta. La risposta accettata ha un paio di difetti che possono confondere i principianti.
Flamefire,

24

Vedi se questo aiuta:

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

In caso contrario, utilizzare il programma xsd.exe fornito con Visual Studio per creare un documento dello schema basato su quel file XML, quindi utilizzarlo nuovamente per creare una classe basata sul documento dello schema.


9

Non credo che .net sia "esigente riguardo alle matrici di deserializzazione". Il primo documento XML non è ben formato. Non esiste alcun elemento radice, anche se sembra che ci sia. Il documento xml canonico ha una radice e almeno 1 elemento (se non del tutto). Nel tuo esempio:

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

7

prova questo blocco di codice se il tuo file .xml è stato generato da qualche parte nel disco e se hai usato 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.xmlè il percorso del mio file .xml. Puoi cambiarlo per le tue esigenze.


6

La risposta di Kevin è buona, a parte il fatto che nel mondo reale, spesso non sei in grado di modificare l'XML originale in base alle tue esigenze.

Esiste anche una soluzione semplice per l'XML originale:

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

E poi puoi semplicemente chiamare:

var ser = new XmlSerializer(typeof(XmlData));
XmlData data = (XmlData)ser.Deserialize(XmlReader.Create(PathToCarsXml));

Grazie! La tua risposta è esattamente ciò di cui avevo bisogno, in quanto non volevo modificare i file di registro di gigabyte.
Colin,

Anche se vale la pena ricordare che la soluzione XmlSerializer è molto elegante ma, certamente, anche non molto veloce e reagisce sensibilmente a dati Xml imprevisti. Quindi, se il tuo problema non richiede una completa deserializzazione, dovresti considerare di utilizzare solo la classe XmlReader più pragmatica e performante e scorrere gli elementi <Car>.
Kim Homann,

5

Prova questa classe generica per serializzazione e deserializzazione 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;
    }
}

4

Per principianti

Ho trovato le risposte qui molto utili, che mi hanno detto che ho ancora difficoltà (solo un po ') per farlo funzionare. Quindi, nel caso in cui aiuti qualcuno spiegherò la soluzione funzionante:

XML dalla domanda originale. Il file xml si trova in un file Class1.xml, a pathper questo file viene utilizzato nel codice per individuare questo file xml.

Ho usato la risposta di @erymski per farlo funzionare, quindi ho creato un file chiamato Car.cs e ho aggiunto quanto segue:

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

L'altro bit di codice fornito da @erymski ...

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

... entra nel tuo programma principale (Program.cs), in static CarCollection XCar()questo modo:

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

Spero che sia d'aiuto :-)


1
Ha funzionato per me. Questa è una soluzione perfettamente funzionante anche per il dato input xml (come nell'esempio di OP). [XmlElement ("Car")] è l'attributo giusto. In altri esempi hanno usato XmlArray ecc. Che non sono necessari fintanto che abbiamo la proprietà definita come pubblica Car [] Cars {get; impostato; } e lo deserializzerebbe correttamente. Grazie.
Diwakar Padmaraja,

3

Che ne dici di una classe generica per deserializzare 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;
}

Questa parte può o potrebbe non essere necessaria. Apri il documento XML in Visual Studio, fai clic con il tasto destro del mouse su XML, scegli le proprietà. Quindi scegli il tuo file di schema.


1
Ciò mi ha permesso di ridurre un po 'il codice della logica aziendale e centralizzare la funzionalità in una classe di supporto con tutte le classi <T> che ho generato. Avevo già l'XML in una stringa, quindi potrei condensarlo in questo: `T LoadXMLFileIntoClass <T> statico pubblico (string xmlData)` {`Serializer XmlSerializer = new XmlSerializer (typeof (T)); `return (T) serializer.Deserialize (new StringReader (xmlData)); `} Grazie!
pwrgreg007,

3

Una fodera:

var object = (Cars)new XmlSerializer(typeof(Cars)).Deserialize(new StringReader(xmlString));

2

L'idea è di gestire tutti i livelli per la deserializzazione. Vedi una soluzione di esempio che abbia risolto il mio problema simile

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

L'XML sopra è gestito su due livelli

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

}

Il livello interiore

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

}

Allo stesso modo è necessario più livelli con car as array Controlla questo esempio per la deserializzazione multilivello


1

Se ricevi errori usando xsd.exe per creare il tuo file xsd, usa la classe XmlSchemaInference come menzionato in msdn . Ecco un unit test per dimostrare:

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

1

Puoi semplicemente modificare un attributo per la tua proprietà di auto Cars da XmlArrayItem a XmlElment. Cioè, da

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

per

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

1

La mia soluzione:

  1. Utilizzare Edit > Past Special > Paste XML As Classesper ottenere la classe nel tuo codice
  2. Prova qualcosa del genere: crea un elenco di quella classe ( List<class1>), quindi usa il XmlSerializerper serializzare quell'elenco in un xmlfile.
  3. Ora sostituisci semplicemente il corpo di quel file con i tuoi dati e provaci deserialize.

Codice:

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: è necessario prestare attenzione al nome della radice, non modificarlo. Il mio è "ArrayOfClass1"


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

1
Inserisci la tua risposta sempre nel contesto anziché semplicemente incollare il codice. Vedi qui per maggiori dettagli.
gehbiszumeis,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.