Sérialisation xml d'un dictionary

Soyez le premier à donner votre avis sur cette source.

Vue 12 155 fois - Téléchargée 314 fois

Description

/*
  • Problématique :
  • Les tableaux, List, Collection et autres, contenus dans des objets
  • sont facilement sérialisables en XML dans la version 2.0 du Framework.
  • Mais les Framework 1.1 et 2.0 ne permettent cependant pas de sérialiser
  • des objets issus de la classe : Dictionary, qui est une classe plus complexe.
  • Objectifs :
  • Sérialiser un Dictionary sous forme XML
  • Désérialiser le document XML pour reconstituer le Dictionary
  • Remarque :
  • La sérialisation binaire n'est pas abordée ici.
  • Solution proposée :
  • Comme les Dictionary ne sont pas directement sérialisables en XML,
  • il faut enrichir les Dictionary proposés, par héritage, pour leurs ajouter
  • les méthodes nécessaires à leur sérialisation XML :
  • - Le Framework 1.1 (Visual Studio 2003) permettait de personnaliser
  • les Dictionary par héritage de spécialisation de la classe
  • abstraite DictionaryBase.
  • - Le Framework 2.0 (Visual Studio 2005) offre une autre approche.
  • On peut hériter de l'interface IDictionary pour personnaliser
  • son dictionnaire, et en héritant simultanément de l'interface
  • IXmlSerializable, qui apporte la solution à notre besoin de
  • sérialisation XML.
  • De nombreuses méthodes semblent alors devoir être implémentées...
  • Fonctionnement :
  • L'objet "mesClients", issu de la classe "Clients", contient deux "Client".
  • Elle est sérialisée dans le fichier : MonFichierXml1.xml
  • Ce fichier est désérialisé pour reconstituer un objet "tesClients"
  • qui est lui aussi sérialisé dans un autre fichier : MonFichierXml2.xml
  • Reste ensuite à comparer les deux fichiers pour s'assurer que l'on a rien
  • perdu au passage ...
  • Les fichiers de sérialisation sont normalement identiques.
  • Mais pas les objets "Client" qui ont perdu leur champs "private", normal.
  • Remarque :
  • De nombreuses classes du Framework 2.0 sont maintenant dites génériques.
  • Cette généricité permet de limiter les objets contenus dans une collection
  • par exemple.
  • Cet exemple utilise des classes génériques.
  • - Ancienne syntaxe
  • using System.Collections;
  • public Dictionary mesClients;
  • Et on pouvait alors mettre presque n'importe quoi dans ce dictionnaire !
  • - Nouvelle syntaxe
  • using System.Collections.Generic;
  • public Dictionary<String, Client> mesClients;
  • On ne peut mettre que des Client dans ce dictionnaire, avec une clef String.
  • Questions :
  • - Quelques points restent ici sans réponse (questions dans le code).
  • - Les événements d'erreur de la désérialisation semblent ne pas se déclencher !
  • - Dans WriteXml, la clef du dictionnaire génère la balise <string>dur</string>,
  • Comment obtenir <Clef>dur</Clef>, sans transformer le String en objet ?
  • - Est-il possible de forcer la sérialisation d'un champs "private" : m_DateNaissance ?
  • - L'utilisation de "using" comme instruction, afin de limiter la portée d'un variable
  • Essayez de le supprimer dans "Sérialisateur", et la désérialisation plante !
  • Si vous trouvez une solution à ces questions, je serais heureux d'en profiter.
  • Si vous pensez à d'autres techniques pour résoudre tout, ou partie, de ce problème
  • je serais content d'apprendre quelque chose de votre part. Merci à vous.
  • Une partie du code a été reprise sur le site MSDN de Microsoft. Merci à eux.
  • Si l'orthographe de ce document présente des lacunes, n'hésitez pas à m'en informer,
  • je serais attentif à vos remarques. (C'est sûr qu'il y a quelques erreurs !)
  • Environnement technique :
  • - Windows XP Pro,
  • - Visual Studio 2005,
  • - C# 2.0,
  • - XML.
  • /

Source / Exemple :


using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Xml;
using System.IO;

namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        /* 

  • Champs
  • /
String NomFichier1 = "MonFichierXml1.xml"; String NomFichier2 = "MonFichierXml2.xml"; Clients mesClients; Clients tesClients; // Sérialisateur XmlSerializer monSérialisateur = null; /*
  • Contructeurs
  • /
public Form1() { InitializeComponent(); } /*
  • Méthodes événementielles
  • /
private void Form1_Load(object sender, EventArgs e) { Client unClient; // Création de la collection des clients mesClients = new Clients(); // Ajout d'un client unClient = new Client("100", "Durand", "Pierre", @"01/02/2003"); mesClients.Add("dur", unClient); // Ajout d'un autre client unClient = new Client("200", "Martin", "Jean", @"04/05/2006"); mesClients.Add("mar", unClient); } private void Sérialiser_Click(object sender, EventArgs e) { Sérialiseur(mesClients, NomFichier1); MessageBox.Show("Consultez le fichier " + NomFichier1 + " afin de vérifier son contenu ..."); } private void Désérialiser_Click(object sender, EventArgs e) { tesClients = Désérialiseur(NomFichier1); Sérialiseur(tesClients, NomFichier2); // Pour vérification, par comparaison, avec NomFichier1. MessageBox.Show("Comparez les fichiers " + NomFichier1 + " et " + NomFichier2 + " afin de vérifier leur identité..."); } private void Quitter_Click(object sender, EventArgs e) { Application.Exit(); } /*
  • Méthodes
  • /
void Sérialiseur(Clients parClients, String parNomFichier) { try { /*
  • Création d'un sérialiseur de type : XmlSerializer
  • spécifiant le type de classe sérialisable : Clients.
  • /
monSérialisateur = new XmlSerializer(typeof(Clients)); // Paramétrage de l'écriture XML XmlWriterSettings ParamètresSérialisation = new XmlWriterSettings(); ParamètresSérialisation.Indent = true; ParamètresSérialisation.IndentChars = (" "); /*
  • Sérialisation
  • On peut utiliser aussi simplement : StreamWriter, ou FileWriter.
  • /
using (XmlWriter monEcrivain = XmlWriter.Create(parNomFichier, ParamètresSérialisation)) { monSérialisateur.Serialize(monEcrivain, mesClients); monEcrivain.Flush(); } } catch (Exception ex) { MessageBox.Show(ex.Message + "\n" + ex.InnerException.Message); } } Clients Désérialiseur(String parNomFichier) { /*
  • Création d'un sérialiseur de type : XmlSerializer
  • spécifiant le type de classe sérialisable : Clients.
  • /
monSérialisateur = new XmlSerializer(typeof(Clients)); // If the XML document has been altered with unknown // nodes or attributes, handles them with the // UnknownNode and UnknownAttribute events. monSérialisateur.UnknownNode +=new XmlNodeEventHandler(monSérialisateur_UnknownNode); monSérialisateur.UnknownAttribute += new XmlAttributeEventHandler(monSérialisateur_UnknownAttribute); // Paramétrage de l'écriture XML XmlReaderSettings ParamètresSérialisation = new XmlReaderSettings(); ParamètresSérialisation.ConformanceLevel = ConformanceLevel.Fragment; ParamètresSérialisation.IgnoreWhitespace = true; ParamètresSérialisation.IgnoreComments = true; // On peut utiliser aussi simplement : StreamReader, ou FileReader. XmlReader monLecteur = XmlReader.Create(parNomFichier, ParamètresSérialisation); /*
  • Uses the Deserialize method to restore the object's state
  • with data from the XML document.
  • /
return (Clients)monSérialisateur.Deserialize(monLecteur); } /*
  • Gestionnaires d'erreurs
  • /
private void monSérialisateur_UnknownNode(object sender, XmlNodeEventArgs e) { MessageBox.Show("Noeud inconnu : " + e.Name + "\t" + e.Text); } private void monSérialisateur_UnknownAttribute(object sender, XmlAttributeEventArgs e) { System.Xml.XmlAttribute attr = e.Attr; MessageBox.Show("Attribut inconnu : " + attr.Name + "='" + attr.Value + "'"); } } } public class Clients : IDictionary<String, Client>, IXmlSerializable /*
  • Toutes les interfaces de IDictionary sont implémentées par
  • simple "re-branchement" aux méthodes de : Dictionary.
  • La seule partie "créative" concerne les méthodes
  • de IXmlSerializable : GetSchema, WriteXml et ReadXml.
  • /
{ public IDictionary<String, Client> monDictionnaire; public Clients() { monDictionnaire = new Dictionary<String, Client>(); } #region IDictionary<string,Client> Membres public void Add(string key, Client value) { monDictionnaire.Add(key, value); } public bool ContainsKey(string key) { return monDictionnaire.ContainsKey(key); } public ICollection<string> Keys { get { return monDictionnaire.Keys; } } public bool Remove(string key) { return monDictionnaire.Remove(key); } public bool TryGetValue(string key, out Client value) { return monDictionnaire.TryGetValue(key,out value); } public ICollection<Client> Values { get { return monDictionnaire.Values; } } public Client this[string key] { get { return monDictionnaire[key]; } set { monDictionnaire[key] = value; } } #endregion #region ICollection<KeyValuePair<string,Client>> Membres public void Add(KeyValuePair<string, Client> item) { monDictionnaire.Add(item); } public void Clear() { monDictionnaire.Clear(); } public bool Contains(KeyValuePair<string, Client> item) { return monDictionnaire.Contains(item); } public void CopyTo(KeyValuePair<string, Client>[] array, int arrayIndex) { monDictionnaire.CopyTo(array, arrayIndex); } public int Count { get { return monDictionnaire.Count; } } public bool IsReadOnly { get { return monDictionnaire.IsReadOnly; } } public bool Remove(KeyValuePair<string, Client> item) { return monDictionnaire.Remove(item); } #endregion #region IEnumerable<KeyValuePair<string,Client>> Membres public IEnumerator<KeyValuePair<string, Client>> GetEnumerator() { return monDictionnaire.GetEnumerator(); } #endregion #region IEnumerable Membres System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return monDictionnaire.GetEnumerator(); } #endregion #region IXmlSerializable Membres public System.Xml.Schema.XmlSchema GetSchema() { return null; } public void ReadXml(System.Xml.XmlReader reader) { reader.Read(); // move past container while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { String key = (String)new XmlSerializer(typeof(String)).Deserialize(reader); reader.MoveToContent(); Client value = (Client)new XmlSerializer(typeof(Client)).Deserialize(reader); reader.MoveToContent(); monDictionnaire.Add(key,value); } } public void WriteXml(System.Xml.XmlWriter parEcrivain) { foreach(KeyValuePair<String, Client> unePaire in monDictionnaire) { new XmlSerializer(typeof(String)).Serialize(parEcrivain, unePaire.Key); // Comment remplacer ici <string> par <Clef> ? new XmlSerializer(typeof(Client)).Serialize(parEcrivain, unePaire.Value); } } #endregion } public class Client { /*
  • Ce champs sera sérialisé, car il est public
  • Mais le nom de la balise n'est pas joli pour XML !
  • /
public String m_CodeClient; /*
  • Ce champs sera sérialisé, car il est public
  • Mais le nom de la balise est modifié
  • pour être plus joli dans XML !
  • /
[XmlElement(ElementName="NomClient")] public String m_NomClient; /*
  • Celui-ci ne sera pas sérialisé, car il est privé
  • mais la propriété : PrénomClient, permettra la sérialisation !
  • /
private String m_PrénomClient; /*
  • Ce champs ne sera pas sérialisé du tout
  • car il est privé, sans propriété ...
  • /
private String m_DateNaissance; // Est-il possible de sérialiser ce champs, privé, par un attribut particulier ? public Client() { } public Client(String parCodeClient, String parNomClient, String parPrénomClient, String parDateNaissance) { m_CodeClient = parCodeClient; m_NomClient = parNomClient; PrénomClient = parPrénomClient; m_DateNaissance = parDateNaissance; } public String PrénomClient { get { return m_PrénomClient; } set { m_PrénomClient = value; } } }

Conclusion :

  • Questions :
  • - Quelques points restent ici sans réponse (questions dans le code).
  • - Les événements d'erreur de la désérialisation semblent ne pas se déclencher !
  • - Dans WriteXml, la clef du dictionnaire génère la balise <string>dur</string>,
  • Comment obtenir <Clef>dur</Clef>, sans transformer le String en objet ?
  • - Est-il possible de forcer la sérialisation d'un champs "private" : m_DateNaissance ?
  • - L'utilisation de "using" comme instruction, afin de limiter la portée d'un variable
  • Essayez de le supprimer dans "Sérialisateur", et la désérialisation plante !
  • Si vous trouvez une solution à ces questions, je serais heureux d'en profiter.
  • Si vous pensez à d'autres techniques pour résoudre tout, ou partie, de ce problème
  • je serais content d'apprendre quelque chose de votre part. Merci à vous.
  • Une partie du code a été reprise sur le site MSDN de Microsoft. Merci à eux.
  • Si l'orthographe de ce document présente des lacunes, n'hésitez pas à m'en informer,
  • je serais attentif à vos remarques. (C'est sûr qu'il y a quelques erreurs !)

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

elassas
Messages postés
3
Date d'inscription
mardi 25 novembre 2008
Statut
Membre
Dernière intervention
24 juillet 2009
-
Voilà une autre solution :

Dans cette classe je sérialise un Dictionary en binaire et en XML, pour tester la classe il suffit de faire une classe qui contient une méthode main() et bien evidemement créer un dico le sérialiser puis le désérialiser bon bref ça je pense aue tout le monde sais le faire.

J'ai aussi créer une méthode Display() pour afficher les dico afin de vérifier si l'objet est bien sérialisé et bien désérialisé.

Si vous avez des questions je suis joignable par mail.

Code de ma classe :

namespace Dictionary
{
using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Xml.Serialization;

[Serializable]
public class Dictionary<TKey, TValue> : System.Collections.Generic.Dictionary<TKey, TValue>, IXmlSerializable, ISerializable
{
#region FIELDS
public enum SerializationLevel
{
Minimal,
Full
}
private SerializationLevel _SerializationLevel;
#endregion

#region ISerializable Members
/// <summary>
///
/// </summary>
///

///

public Dictionary(SerializationInfo info, StreamingContext context)
{
SerializationInfoEnumerator InfoEnumerator = info.GetEnumerator();
while (InfoEnumerator.MoveNext())
{
System.Collections.DictionaryEntry DE = (System.Collections.DictionaryEntry)InfoEnumerator.Value;
this.Add((TKey)DE.Key, (TValue)DE.Value);
//System.Collections.Generic.KeyValuePair<TKey, TValue> kpv = (System.Collections.Generic.KeyValuePair<TKey, TValue>)InfoEnumerator.Value;
//this.Add(kpv.Key, kpv.Value);
}
}
/// <summary>
///
/// </summary>
///

///

[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public new virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info != null)
foreach (System.Collections.Generic.KeyValuePair<TKey, TValue> kpv in this)
info.AddValue(kpv.Key.ToString(), new System.Collections.DictionaryEntry(kpv.Key, kpv.Value), typeof(System.Collections.DictionaryEntry));
//info.AddValue(kpv.Key.ToString(), kpv, typeof(System.Collections.Generic.KeyValuePair<TKey, TValue>));
}
#endregion

#region Constructors
/// <summary>
///
/// </summary>
public Dictionary()
: base()
{
this._SerializationLevel = Dictionary<TKey, TValue>.SerializationLevel.Full;
}
/// <summary>
///
/// </summary>
///

public Dictionary(SerializationLevel serializationLevel)
: base()
{
this._SerializationLevel = serializationLevel;
}
#endregion

#region IXmlSerializable Members
/// <summary>
///
/// </summary>
/// <returns></returns>
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
/// <summary>
///
/// </summary>
///

public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(DictionaryEntry));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty) return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
DictionaryEntry KP = (DictionaryEntry)xmlSerializer.Deserialize(reader);
this.Add((TKey)KP.Key, (TValue)KP.Value);
reader.MoveToContent();
}
reader.ReadEndElement();
}
/// <summary>
///
/// </summary>
///

public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty);
if (this._SerializationLevel == Dictionary<TKey, TValue>.SerializationLevel.Full)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(System.Collections.DictionaryEntry), new Type[] { typeof(TValue), typeof(TKey) });
foreach (System.Collections.Generic.KeyValuePair<TKey, TValue> kpv in this)
{
xmlSerializer.Serialize(writer, new System.Collections.DictionaryEntry(kpv.Key, kpv.Value));
}
}
else
{
XmlSerializer deSerializer = new XmlSerializer(typeof(DictionaryEntry));
foreach (System.Collections.Generic.KeyValuePair<TKey, TValue> kpv in this)
{
deSerializer.Serialize(writer, new DictionaryEntry((TKey)kpv.Key, (TValue)kpv.Value), ns);
}
}
}
#endregion

#region Methods
public void Display()
{
IDictionaryEnumerator myEnumerator = this.GetEnumerator();
while (myEnumerator.MoveNext())
Console.WriteLine(myEnumerator.Key.ToString() + " : " + myEnumerator.Value.ToString());
Console.WriteLine();
}
#endregion
}
}
godvicien
Messages postés
36
Date d'inscription
dimanche 23 janvier 2005
Statut
Membre
Dernière intervention
6 avril 2014
-
Très bon code, je mets 8/10.
nargho
Messages postés
1
Date d'inscription
dimanche 16 juillet 2006
Statut
Membre
Dernière intervention
7 octobre 2007
-
Salut,

Le code a très bien marché pour moi, mais j'ai trouvé une petite erreur dans la désérialisation.
Si on a par exemple deux dictionnaires a remplir à partir d'un fichier xml tel que :

<DicoClients>
<clients-fr>
<Client NomClient="toto" />
<Client NomClient="toto2" />
</clients-fr>
<clients-en>
...
</clients-en>
</DicoClients>

Dans la classe :

[XmlRoot("DicoClients")]
public class DicoClients
{
private Clients _clientsFr;
[XmlElement("clients-fr")]
public Clients ClientsFr
{
get { return _clientsFr; }
set { _clientsFr = value; }
}

private Clients _clientsEn;
[XmlElement("clients-en")]
public Clients ClientsEn
{
get { return _clientsEn; }
set { _clientsEn = value; }
}
}

Le problème est que la désérialisation du premier dictionnaire se fait bien mais pas la 2ème. Pour corriger cela, il faut modifier la méthode ReadXml :

public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read(); // move past container
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
Client value = (Client)new XmlSerializer(typeof(Client)).Deserialize(reader);
reader.MoveToContent();
monDictionnaire.Add(value.m_NomClient,value);
}
reader.Read(); // <---------- On se place sur l'élément suivant
}

Voilou :o)
BaFM
Messages postés
64
Date d'inscription
mercredi 24 juillet 2002
Statut
Membre
Dernière intervention
26 novembre 2009
-
Quelques éléments de réponse aux questions :
- Dans WriteXml, la clef du dictionnaire génère la balise <string>dur</string>, Comment obtenir <Clef>dur</Clef>, sans transformer le String en objet ?
En faut, il faut utilise XmlAttributeOverrides pour ajouter la prise en compte de nouveaux attributs personnalisés. Ou alors, juste donner un nom d'élément racine lors de la création du serialiseur.

- Est-il possible de forcer la sérialisation d'un champs "private" : m_DateNaissance ?
Il me semble, d'après la documentation, que lorsque le type n'est pas une collection et donc que ses champs publics sont sérialisés, quand on ajoute l'interface IXmlSerializable, celle-ci permet de rajouter les données XML de notre choix. Mais je me trompe peut-être. J'ai pas essayé, mais il y a un exemple.

Sinon, il est mieux je pense d'enregistrer tes serialiseurs dans des champs static. Ce qui permet de ne pas le reconstruire. Mais ptet qu'il y a une sorte de système de correspondance pour ne pas recréer une assembly à chaque noouvelle instance de serialiseur...
hsaturn
Messages postés
14
Date d'inscription
jeudi 6 octobre 2005
Statut
Membre
Dernière intervention
3 juin 2007
-
Bonjour

Grand merci pour ce code.

J'ai eu beaucoup de mal à faire fonctionner le fichier txt (sic).
Mais ça a l'air de fonctionner correctement. Je vais utiliser ce bout de code très sympa :-)

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.