Est-il possible de désérialiser XML dans List <T>?

155

Compte tenu du XML suivant:

<?xml version="1.0"?>
<user_list>
   <user>
      <id>1</id>
      <name>Joe</name>
   </user>
   <user>
      <id>2</id>
      <name>John</name>
   </user>
</user_list>

Et la classe suivante:

public class User {
   [XmlElement("id")]
   public Int32 Id { get; set; }

   [XmlElement("name")]
   public String Name { get; set; }
}

Est-il possible d'utiliser XmlSerializerpour désérialiser le XML en un List<User>? Si tel est le cas, quel type d'attributs supplémentaires dois-je utiliser ou quels paramètres supplémentaires dois-je utiliser pour construire l' XmlSerializerinstance?

Un tableau ( User[]) serait acceptable, mais un peu moins préférable.

Daniel Schaffer
la source

Réponses:

137

Vous pouvez encapsuler la liste de manière simple:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;

[XmlRoot("user_list")]
public class UserList
{
    public UserList() {Items = new List<User>();}
    [XmlElement("user")]
    public List<User> Items {get;set;}
}
public class User
{
    [XmlElement("id")]
    public Int32 Id { get; set; }

    [XmlElement("name")]
    public String Name { get; set; }
}

static class Program
{
    static void Main()
    {
        XmlSerializer ser= new XmlSerializer(typeof(UserList));
        UserList list = new UserList();
        list.Items.Add(new User { Id = 1, Name = "abc"});
        list.Items.Add(new User { Id = 2, Name = "def"});
        list.Items.Add(new User { Id = 3, Name = "ghi"});
        ser.Serialize(Console.Out, list);
    }
}
Marc Gravell
la source
5
Belle solution avec le [XmlElement ("user")] pour éviter un niveau supplémentaire d'éléments. En regardant cela, j'ai pensé avec certitude qu'il aurait émis un nœud <user> ou <Items> (si vous n'aviez pas l'attribut XmlElement), puis ajouter des nœuds <user> en dessous. Mais je l'ai essayé et ce n'est pas le cas, émettant ainsi exactement ce que la question voulait.
Jon Kragh
Et si j'avais deux listes sous UserList ci-dessus? J'ai essayé votre méthode et elle dit qu'elle définit déjà un membre appelé XYZ avec les mêmes types de paramètres
Kala J
Je ne sais pas pourquoi c'est marqué comme bonne réponse. Cela inclut l'ajout d'une classe pour envelopper la liste. C'est certainement ce que la question essaie d'éviter.
DDRider62
1
@ DDRider62 la question ne dit pas "sans emballage". La plupart des gens sont assez pragmatiques et veulent juste sortir les données. Cette réponse vous permet de le faire, via le .Itemsmembre.
Marc Gravell
50

Si vous décorez la Userclasse avec le XmlTypepour correspondre à la capitalisation requise:

[XmlType("user")]
public class User
{
   ...
}

Ensuite, le XmlRootAttributesur le XmlSerializerctor peut fournir la racine souhaitée et permettre une lecture directe dans List <>:

    // e.g. my test to create a file
    using (var writer = new FileStream("users.xml", FileMode.Create))
    {
        XmlSerializer ser = new XmlSerializer(typeof(List<User>),  
            new XmlRootAttribute("user_list"));
        List<User> list = new List<User>();
        list.Add(new User { Id = 1, Name = "Joe" });
        list.Add(new User { Id = 2, Name = "John" });
        list.Add(new User { Id = 3, Name = "June" });
        ser.Serialize(writer, list);
    }

...

    // read file
    List<User> users;
    using (var reader = new StreamReader("users.xml"))
    {
        XmlSerializer deserializer = new XmlSerializer(typeof(List<User>),  
            new XmlRootAttribute("user_list"));
        users = (List<User>)deserializer.Deserialize(reader);
    }

Crédit: basé sur la réponse de YK1 .

richaux
la source
11
À mon avis, c'est clairement LA réponse à la question. La question portait sur la désérialisation dans List <T>. Toutes les autres solutions, sauf peut-être une, incluent une classe d'encapsulation pour contenir la liste, ce qui n'était certainement pas la question posée, et ce que l'auteur de la question semble essayer d'éviter.
DDRider62
1
Avec cette approche, le XmlSerializerdoit être mis en cache statiquement et réutilisé pour éviter une fuite de mémoire grave, consultez Fuite de mémoire à l'aide de StreamReader et XmlSerializer pour plus de détails.
dbc
16

Oui, il sérialisera et désérialisera un List <>. Assurez-vous simplement d'utiliser l'attribut [XmlArray] en cas de doute.

[Serializable]
public class A
{
    [XmlArray]
    public List<string> strings;
}

Cela fonctionne avec Serialize () et Deserialize ().

Coincoin
la source
16

Je pense avoir trouvé un meilleur moyen. Vous n'êtes pas obligé de mettre des attributs dans vos classes. J'ai créé deux méthodes pour la sérialisation et la désérialisation qui prennent une liste générique comme paramètre.

Jetez un œil (cela fonctionne pour moi):

private void SerializeParams<T>(XDocument doc, List<T> paramList)
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(paramList.GetType());

        System.Xml.XmlWriter writer = doc.CreateWriter();

        serializer.Serialize(writer, paramList);

        writer.Close();           
    }

private List<T> DeserializeParams<T>(XDocument doc)
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(List<T>));

        System.Xml.XmlReader reader = doc.CreateReader();

        List<T> result = (List<T>)serializer.Deserialize(reader);
        reader.Close();

        return result;
    }

Vous pouvez donc sérialiser la liste que vous voulez! Vous n'avez pas besoin de spécifier le type de liste à chaque fois.

        List<AssemblyBO> list = new List<AssemblyBO>();
        list.Add(new AssemblyBO());
        list.Add(new AssemblyBO() { DisplayName = "Try", Identifier = "243242" });
        XDocument doc = new XDocument();
        SerializeParams<T>(doc, list);
        List<AssemblyBO> newList = DeserializeParams<AssemblyBO>(doc);
tudor.iliescu
la source
3
Merci d'avoir répondu à la question. J'ajouterais que pour List<MyClass>l'élément document devrait être nommé ArrayOfMyClass.
Max Toro
8

Oui, il se désérialise en List <>. Pas besoin de le conserver dans un tableau et de l'envelopper / l'encapsuler dans une liste.

public class UserHolder
{
    private List<User> users = null;

    public UserHolder()
    {
    }

    [XmlElement("user")]
    public List<User> Users
    {
        get { return users; }
        set { users = value; }
    }
}

Code de désérialisation,

XmlSerializer xs = new XmlSerializer(typeof(UserHolder));
UserHolder uh = (UserHolder)xs.Deserialize(new StringReader(str));
Nemo
la source
5

Je ne suis pas sûr de List <T> mais les tableaux sont certainement faisables. Et un peu de magie rend très facile d'accéder à nouveau à une liste.

public class UserHolder {
   [XmlElement("list")]
   public User[] Users { get; set; }

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
la source
2
Est-il possible de se passer de la classe «titulaire»?
Daniel Schaffer
@Daniel, AFAIK, non. Vous devez sérialiser et désérialiser en un type d'objet concret. Je ne crois pas que la sérialisation XML prend en charge nativement les classes de collection comme début d'une sérialisation. Cependant, je ne le sais pas à 100%.
JaredPar
[XmlElement ("list")] doit être [XmlArray ("list")] à la place. C'est la seule façon dont la désérialisation a fonctionné pour moi dans .NET 4.5
eduardobr
2

Que diriez-vous

XmlSerializer xs = new XmlSerializer(typeof(user[]));
using (Stream ins = File.Open(@"c:\some.xml", FileMode.Open))
foreach (user o in (user[])xs.Deserialize(ins))
   userList.Add(o);    

Pas particulièrement sophistiqué mais cela devrait fonctionner.

PRJ
la source
2
Bienvenue dans stackoverflow! Il est toujours préférable de fournir une brève description d'un exemple de code pour améliorer la précision de la publication :)
Picrofo Software