Czy można deserializować XML do List <T>?

155

Biorąc pod uwagę następujący kod XML:

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

I następująca klasa:

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

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

Czy można użyć XmlSerializerdo deserializacji XML do pliku List<User>? Jeśli tak, jakiego typu dodatkowych atrybutów będę potrzebować lub jakich dodatkowych parametrów muszę użyć do skonstruowania XmlSerializerinstancji?

Tablica ( User[]) byłaby dopuszczalna, choć nieco mniej preferowana.

Daniel Schaffer
źródło

Odpowiedzi:

137

Listę można zamknąć w trywialny sposób:

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
źródło
5
Dobre rozwiązanie z [XmlElement ("user")], aby uniknąć dodatkowego poziomu elementów. Patrząc na to, pomyślałem na pewno, że wyemituje węzeł <user> lub <Items> (jeśli nie masz atrybutu XmlElement), a następnie dodaj węzły <user> pod nim. Ale spróbowałem i nie udało się, więc emitowałem dokładnie to, czego dotyczyło pytanie.
Jon Kragh
A co jeśli mam dwie listy pod UserList powyżej? Wypróbowałem twoją metodę i mówi, że już definiuje element o nazwie XYZ z tymi samymi typami parametrów
Kala J
Nie wiem, dlaczego to oznaczono jako właściwą odpowiedź. Obejmuje dodanie klasy do zawijania listy. Z pewnością tego właśnie pytanie próbuje uniknąć.
DDRider62
1
@ DDRider62 pytanie nie mówi „bez zawijania”. Większość ludzi jest bardzo pragmatyczna i chce po prostu wydobyć dane. Ta odpowiedź pozwala ci to zrobić za pośrednictwem .Itemsczłonka.
Marc Gravell
50

Jeśli ozdobisz Userklasę znakiem, XmlTypeaby dopasować wymaganą wielkość liter:

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

Następnie XmlRootAttributena XmlSerializerctor może podać żądany katalog główny i umożliwić bezpośredni odczyt do listy <>:

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

Kredyt: na podstawie odpowiedzi z YK1 .

Richaux
źródło
11
Z mojego punktu widzenia jest to wyraźnie odpowiedź na to pytanie. Pytanie dotyczyło deserializacji do List <T>. Wszystkie inne rozwiązania, z wyjątkiem jednego, zawierają klasę opakowującą, która zawiera listę, która z pewnością nie była zadawanym pytaniem i czego autor pytania najwyraźniej stara się uniknąć.
DDRider62
1
W tym podejściu XmlSerializernależy statycznie buforować i ponownie używać, aby uniknąć poważnego przecieku pamięci, zobacz wyciek pamięci przy użyciu StreamReader i XmlSerializer, aby uzyskać szczegółowe informacje.
dbc
16

Tak, będzie serializować i deserializować List <>. W razie wątpliwości upewnij się tylko, że używasz atrybutu [XmlArray].

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

Działa to zarówno z Serialize (), jak i Deserialize ().

Coincoin
źródło
16

Myślę, że znalazłem lepszy sposób. Nie musisz umieszczać atrybutów w swoich klasach. Zrobiłem dwie metody serializacji i deserializacji, które przyjmują listę ogólną jako parametr.

Spójrz (u mnie działa):

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

Możesz więc serializować dowolną listę! Nie musisz za każdym razem określać typu listy.

        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
źródło
3
Dzięki za odpowiedź na pytanie. Dodam, że dla List<MyClass>elementu dokumentu należy nazwać ArrayOfMyClass.
Max Toro
8

Tak, deserializacja do List <>. Nie ma potrzeby trzymania go w tablicy i zawijania / hermetyzacji na liście.

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

    public UserHolder()
    {
    }

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

Kod deserializacji,

XmlSerializer xs = new XmlSerializer(typeof(UserHolder));
UserHolder uh = (UserHolder)xs.Deserialize(new StringReader(str));
Nemo
źródło
5

Nie jestem pewien co do List <T>, ale tablice są z pewnością możliwe. Odrobina magii sprawia, że ​​ponowne przejście do Listy jest naprawdę łatwe.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
źródło
2
Czy można obejść się bez klasy „posiadacza”?
Daniel Schaffer,
@Daniel, AFAIK, nie. Musisz serializować i deserializować do konkretnego typu obiektu. Nie wierzę, że serializacja XML natywnie obsługuje klasy kolekcji jako początek serializacji. Nie wiem jednak w 100%.
JaredPar
[XmlElement ("list")] powinno zamiast tego mieć wartość [XmlArray ("list")]. To jedyny sposób Deserializacji pracował dla mnie w .NET 4.5
eduardobr
2

Co powiesz na

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

Niezbyt wyszukane, ale powinno działać.

PRJ
źródło
2
Witamy w stackoverflow! Zawsze lepiej jest podać krótki opis przykładowego kodu, aby poprawić dokładność postów :)
Picrofo Software