É possível desserializar XML na lista <T>?

155

Dado o seguinte 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>

E a seguinte classe:

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

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

É possível usar XmlSerializerpara desserializar o xml em um List<User>? Em caso afirmativo, que tipo de atributos adicionais eu precisarei usar ou quais parâmetros adicionais eu preciso usar para construir a XmlSerializerinstância?

Uma matriz ( User[]) seria aceitável, se um pouco menos preferível.

Daniel Schaffer
fonte

Respostas:

137

Você pode encapsular a lista trivialmente:

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
fonte
5
Solução agradável com o [XmlElement ("user")] para evitar um nível extra de elementos. Olhando para isso, pensei com certeza que ele teria emitido um nó <user> ou <Items> (se você não tivesse o atributo XmlElement) e, em seguida, adicione nós <user> sob esse. Mas eu tentei e não, emitindo exatamente o que a pergunta queria.
31810 Jon Kragh
E se eu tivesse duas listas na lista de usuários acima? Eu tentei o seu método e ele diz que já define um membro chamado XYZ com os mesmos tipos de parâmetros
Kala J
Não sei por que isso está marcado como resposta certa. Isso inclui adicionar uma classe para quebrar a lista. Isso foi certamente o que a questão está tentando evitar.
DDRider62
1
@ DDRider62 a pergunta não diz "sem quebra". A maioria das pessoas é bem pragmática e só quer divulgar os dados. Esta resposta permite que você faça isso, através do .Itemsmembro.
Marc Gravell
50

Se você decorar a Userclasse com o XmlTypepara corresponder à capitalização requerida:

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

Então o XmlRootAttributeno XmlSerializerctor pode fornecer a raiz desejado e permitir a leitura directa para 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édito: com base na resposta de YK1 .

richaux
fonte
11
No meu ponto de vista, essa é claramente a resposta para a pergunta. A pergunta era sobre desserialização para a Lista <T>. Todas as outras soluções, exceto uma, incluem uma classe de empacotamento para conter a lista, que certamente não foi a pergunta postada, e o que o autor da pergunta parece estar tentando evitar.
DDRider62
1
Com essa abordagem, ele XmlSerializerdeve ser armazenado em cache e reutilizado estaticamente para evitar um vazamento grave de memória, consulte Vazamento de memória usando StreamReader e XmlSerializer para obter detalhes.
dbc
16

Sim, ele serializará e desserializará uma lista <>. Apenas certifique-se de usar o atributo [XmlArray] em caso de dúvida.

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

Isso funciona com Serialize () e Deserialize ().

Coincoin
fonte
16

Eu acho que encontrei uma maneira melhor. Você não precisa colocar atributos em suas classes. Eu criei dois métodos para serialização e desserialização que tomam a lista genérica como parâmetro.

Dê uma olhada (funciona para mim):

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

Então você pode serializar a lista que quiser! Você não precisa especificar o tipo de lista todas as vezes.

        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
fonte
3
Obrigado por realmente responder à pergunta. Eu acrescentaria que para List<MyClass>o elemento do documento deve ser nomeado ArrayOfMyClass.
Max Toro
8

Sim, desserializa para List <>. Não é necessário mantê-lo em uma matriz e envolvê-lo / encapsular em uma lista.

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

    public UserHolder()
    {
    }

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

Desserializando código,

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

Não tenho certeza sobre a Lista <T>, mas as matrizes certamente são possíveis. E um pouco de magia torna muito fácil chegar a uma lista novamente.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
fonte
2
É possível prescindir da classe "holder"?
217 Daniel Schaffer
@ Daniel, AFAIK, não. Você precisa serializar e desserializar em algum tipo de objeto concreto. Não acredito que a serialização XML suporte nativamente as classes de coleção como o início de uma serialização. Eu não sei 100% disso.
JaredPar
[XmlElement ("list")] deve ser [XmlArray ("list")]]. Essa é a única maneira Deserialization trabalhou para mim em .NET 4.5
eduardobr
2

E se

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

Não é particularmente chique, mas deve funcionar.

PRJ
fonte
2
Bem-vindo ao stackoverflow! É sempre melhor para fornecer uma breve descrição de um código de exemplo para melhorar a precisão pós :)
Picrofo Software