Comment désérialiser un document XML


472

Comment désérialiser ce document 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>

J'ai ceci:

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

qui ne semblent pas fonctionner :-(


Je pense que vous devez échapper aux crochets dans votre exemple de document.
harpo

4
Cette réponse est vraiment très bonne: stackoverflow.com/a/19613934/196210
Revious

Réponses:


359

Voici une version de travail. J'ai changé les XmlElementAttributeétiquettes XmlElementparce que dans le xml les valeurs StockNumber, Make et Model sont des éléments, pas des attributs. J'ai également supprimé le reader.ReadToEnd();(cette fonction lit le flux entier et renvoie une chaîne, de sorte que la Deserialize()fonction ne pouvait plus utiliser le lecteur ... la position était à la fin du flux). J'ai également pris quelques libertés avec la dénomination :).

Voici les cours:

[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 fonction Désérialiser:

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

Et le xml légèrement modifié (j'avais besoin d'ajouter un nouvel élément pour envelopper <Cars> ... Net est difficile à désérialiser les tableaux):

<?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
Le [Serializable]est redondant en cas d'utilisation XmlSerializer; XmlSerializerne vérifie simplement jamais cela. De même, la plupart des [Xml...]attributs sont redondants, car ils imitent simplement le comportement par défaut; c'est-à-dire que par défaut une propriété appelée StockNumberest stockée sous la forme d'un élément nommé <StockNumber>- pas besoin d'attributs pour cela.
Marc Gravell

3
Notez que XmlElementAttribute = XmlElement (c'est une fonctionnalité de langage que vous pouvez omettre le suffixe "Attribute") La vraie solution ici est la suppression de l'appel ReadToEnd () et l'ajout d'un nœud racine. Mais il vaut mieux utiliser le code d'erymski qui résout la question (analyser le xml donné)
Flamefire

2
Merci Kevin, mais que se passe-t-il si je supprime la CarsCollection de l'exemple XML. J'ai supprimé Carscollection des classes et du code Deserealize, mais je n'ai pas réussi.
Vikrant

441

Que diriez-vous d'enregistrer le fichier XML dans un fichier et d'utiliser xsd pour générer des classes C #?

  1. Écrivez le fichier sur le disque (je l'ai nommé foo.xml)
  2. Générez le xsd: xsd foo.xml
  3. Générez le C #: xsd foo.xsd /classes

Et voila - et un fichier de code C # qui devrait pouvoir lire les données via XmlSerializer:

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

(inclure les foo.cs générés dans le projet)


6
Tu es l'homme! Merci. pour toute personne qui en a besoin, "chemin" peut être un Stream que vous créez à partir d'une réponse Web comme ceci: var resp = response.Content.ReadAsByteArrayAsync (); var stream = new MemoryStream (resp.Result);
Induster

1
Super idée, mais je n'ai pas pu la faire fonctionner correctement pour mon modèle légèrement plus compliqué avec des lots de tableaux imbriqués. J'ai continué à recevoir des erreurs de conversion de type pour les tableaux imbriqués - plus le schéma de nommage généré laissait à désirer. J'ai donc fini par suivre l'itinéraire personnalisé.
GotDibbs

9
Comment se rendre à xsd.exe
jwillmer

2
xsd.exe est disponible à partir de l'invite de commande de Visual Studio, pas de l'invite de commande Windows. Vérifiez si vous pouvez ouvrir l'invite de commande à partir de Visual Studio sous Outils. Sinon, essayez d'y accéder à partir du dossier Visual Studio. Pour VS 2012, il se trouvait ici: C: \ Program Files (x86) \ Microsoft Visual Studio 12.0 \ Common7 \ Tools \ Shortcuts. Dans Windows 8, essayez de rechercher «Visual Studio Tools».
goku_da_master

2
Pour tous ceux qui recherchent XSD. Voici un fil de discussion SO: stackoverflow.com/questions/22975031/…
SOReader

229

Vous avez deux possibilités.

Méthode 1. Outil XSD


Supposons que vous ayez votre fichier XML à cet emplacement C:\path\to\xml\file.xml

  1. Ouvrez l' invite de commande du développeur
    Vous pouvez le trouver dans Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools Ou si vous avez Windows 8, vous pouvez simplement commencer à taper l' invite de commande du développeur dans l' écran Démarrer
  2. Changez l'emplacement dans votre répertoire de fichiers XML en tapant cd /D "C:\path\to\xml"
  3. Créez un fichier XSD à partir de votre fichier xml en tapantxsd file.xml
  4. Créer des classes C # en tapantxsd /c file.xsd

Et c'est tout! Vous avez généré des classes C # à partir d'un fichier xml dansC:\path\to\xml\file.cs

Méthode 2 - Collage spécial


Visual Studio 2012+ requis

  1. Copiez le contenu de votre fichier XML dans le presse-papiers
  2. Ajoutez à votre solution un nouveau fichier de classe vide ( Shift+ Alt+ C)
  3. Ouvrez ce fichier et cliquez sur le menu Edit > Paste special > Paste XML As Classes
    entrez la description de l'image ici

Et c'est tout!

Usage


L'utilisation est très simple avec cette classe d'assistance:

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

Il ne vous reste plus qu'à:

    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 bonne réponse. Mais, la Paste XML As Classescommande ne cible que .NET 4.5
ravy amiry

1
C'est un excellent moyen de générer le modèle si vous avez installé vs2012 +. J'ai exécuté le nettoyage du code ReSharper par la suite pour utiliser les propriétés automatiques, puis j'ai également fait un autre nettoyage. Vous pouvez générer via cette méthode, puis copier dans un ancien projet si nécessaire.
Scotty.NET

4
Cibler .net4.5 n'est pas un problème. Lancez simplement un projet temporaire avec dotnet4.5, faites-y votre copier-coller et copiez la source dans votre projet actuel.
LosManos

2
où est l'objet ou la classe "catalogue"?
CB4

3
Pour que "Coller XML en tant que classes" apparaisse dans ce menu sur la communauté VS 2017, vous devez avoir installé "ASP.NET et le développement Web". S'il manque, exécutez à nouveau VS installer pour modifier votre installation.
Slion

89

L'extrait suivant devrait faire l'affaire (et vous pouvez ignorer la plupart des attributs de sérialisation):

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
C'est en fait la seule et unique réponse. La réponse acceptée a quelques défauts qui peuvent dérouter les débutants.
Flamefire

24

Voyez si cela aide:

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

À défaut, utilisez le programme xsd.exe fourni avec Visual Studio pour créer un document de schéma basé sur ce fichier XML, puis réutilisez-le pour créer une classe basée sur le document de schéma.


9

Je ne pense pas que .net soit «pointilleux sur la désérialisation des tableaux». Le premier document xml n'est pas bien formé. Il n'y a pas d'élément racine, bien qu'il semble y en avoir. Le document XML canonique a une racine et au moins 1 élément (le cas échéant). Dans votre exemple:

<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

essayez ce bloc de code si votre fichier .xml a été généré quelque part sur le disque et si vous avez utilisé 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();`

Remarque: C:\serialize.xmlest le chemin de mon fichier .xml. Vous pouvez le changer selon vos besoins.


6

L'anser de Kevin est bon, mis à part le fait que dans le monde réel, vous n'êtes souvent pas en mesure de modifier le XML d'origine en fonction de vos besoins.

Il existe également une solution simple pour le XML d'origine:

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

Et puis vous pouvez simplement appeler:

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

Merci! Votre réponse est exactement ce dont j'avais besoin, car je ne voulais pas modifier la valeur en gigaoctets des fichiers journaux.
Colin

Même s'il convient de mentionner que la solution XmlSerializer est très élégante mais certes pas très rapide et réagit avec sensibilité aux données Xml inattendues. Donc, si votre problème ne nécessite pas une désérialisation complète, vous devriez envisager d'utiliser uniquement la classe XmlReader plus pragmatique et performante et parcourir les éléments <Car>.
Kim Homann

5

Essayez cette classe générique pour la sérialisation et la désérialisation 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

Pour les débutants

J'ai trouvé les réponses ici très utiles, cela dit que j'avais encore du mal (juste un peu) à faire fonctionner cela. Donc, au cas où cela aiderait quelqu'un, je définirai la solution de travail:

XML de la question d'origine. Le xml est dans un fichier Class1.xml, un pathà ce fichier est utilisé dans le code pour localiser ce fichier xml.

J'ai utilisé la réponse de @erymski pour que cela fonctionne, j'ai donc créé un fichier appelé Car.cs et ajouté ce qui suit:

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'autre morceau de code fourni par @erymski ...

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

... va dans votre programme principal (Program.cs), static CarCollection XCar()comme ceci:

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

J'espère que cela aide :-)


1
Ça a marché pour moi. C'est aussi une solution qui fonctionne parfaitement pour l'entrée xml donnée (comme dans l'exemple OP). [XmlElement ("Car")] est le bon attribut. Dans d'autres exemples, ils ont utilisé XmlArray, etc. qui ne sont pas nécessaires tant que nous avons la propriété définie comme public Car [] Cars {get; ensemble; } et il le désérialiserait correctement. Merci.
Diwakar Padmaraja

3

Que diriez-vous d'une classe générique pour désérialiser un document 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;
}

Cette partie peut être nécessaire ou non. Ouvrez le document XML dans Visual Studio, faites un clic droit sur le XML, choisissez les propriétés. Choisissez ensuite votre fichier de schéma.


1
Cela m'a permis de réduire un peu le code de logique métier et de centraliser les fonctionnalités dans une classe d'assistance avec toutes les classes <T> que j'ai générées. J'avais déjà le XML dans une chaîne, donc je pouvais le condenser à ceci: `public static T LoadXMLFileIntoClass <T> (string xmlData)` {`XmlSerializer serializer = new XmlSerializer (typeof (T)); `return (T) serializer.Deserialize (new StringReader (xmlData)); `} Merci!
pwrgreg007

3

Bon mot:

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

2

L'idée est de gérer tous les niveaux pour la désérialisation. Veuillez consulter un exemple de solution qui a résolu mon problème similaire.

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

Le XML ci-dessus est géré en deux niveaux

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

}

Le niveau intérieur

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 même manière, vous avez besoin de plusieurs niveaux avec car as array Vérifiez cet exemple pour la désérialisation à plusieurs niveaux


1

Si vous obtenez des erreurs en utilisant xsd.exe pour créer votre fichier xsd, utilisez la classe XmlSchemaInference comme mentionné sur msdn . Voici un test unitaire pour démontrer:

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

Vous pouvez simplement changer un attribut pour votre propriété de voiture Cars de XmlArrayItem à XmlElment. Autrement dit, à partir de

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

à

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

1

Ma solution:

  1. Utilisez Edit > Past Special > Paste XML As Classespour obtenir la classe dans votre code
  2. Essayez quelque chose comme ceci: créez une liste de cette classe ( List<class1>), puis utilisez le XmlSerializerpour sérialiser cette liste dans un xmlfichier.
  3. Il vous suffit maintenant de remplacer le corps de ce fichier par vos données et de l'essayer deserialize.

Code:

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

REMARQUE: vous devez faire attention au nom racine, ne le changez pas. Le mien est "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
Veuillez toujours mettre votre réponse en contexte au lieu de simplement coller du code. Voir ici pour plus de détails.
gehbiszumeis
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.