¿Es posible deserializar XML en List <T>?

155

Dado el siguiente 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>

Y la siguiente clase:

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

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

¿Es posible usar XmlSerializerpara deserializar el xml en a List<User>? Si es así, ¿qué tipo de atributos adicionales necesitaré usar, o qué parámetros adicionales necesito usar para construir la XmlSerializerinstancia?

Una matriz ( User[]) sería aceptable, si es un poco menos preferible.

Daniel Schaffer
fuente

Respuestas:

137

Puede encapsular la 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
fuente
55
Buena solución con el [XmlElement ("usuario")] para evitar un nivel adicional de elementos. Mirando esto, pensé con seguridad que habría emitido un nodo <usuario> o <Artículos> (si no tuviera el atributo XmlElement), y luego agregue nodos <usuario> debajo de eso. Pero lo intenté y no fue así, emitiendo exactamente lo que quería la pregunta.
Jon Kragh
¿Qué pasa si tengo dos listas en UserList arriba? Probé su método y dice que ya define un miembro llamado XYZ con los mismos tipos de parámetros
Kala J
No sé por qué esto está marcado como respuesta correcta. Incluye agregar una clase para ajustar la lista. Eso fue ciertamente lo que la pregunta está tratando de evitar.
DDRider62
1
@ DDRider62 la pregunta no dice "sin envolver". La mayoría de las personas son bastante pragmáticas y solo quieren obtener los datos. Esta respuesta le permite hacer eso, a través del .Itemsmiembro.
Marc Gravell
50

Si decora la Userclase con el XmlTypepara que coincida con la capitalización requerida:

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

Luego, XmlRootAttributeen el XmlSerializerctor puede proporcionar la raíz deseada y permitir la lectura directa en la Lista <>:

    // 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: basado en la respuesta de YK1 .

richaux
fuente
11
En mi punto de vista, esta es claramente LA respuesta a la pregunta. La pregunta era sobre la deserialización en la Lista <T>. Todas las otras soluciones, excepto quizás una, incluyen una clase de ajuste para contener la lista, que ciertamente no era la pregunta publicada, y lo que el autor de la pregunta parece estar tratando de evitar.
DDRider62
1
Con este enfoque, XmlSerializerdebe almacenarse en caché de forma estática y reutilizarse para evitar una pérdida grave de memoria, consulte Fuga de memoria con StreamReader y XmlSerializer para obtener más información.
dbc
16

Sí, serializará y deserializará una Lista <>. Solo asegúrese de usar el atributo [XmlArray] si tiene dudas.

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

Esto funciona con Serialize () y Deserialize ().

Coincoin
fuente
16

Creo que he encontrado una mejor manera. No tienes que poner atributos en tus clases. He hecho dos métodos de serialización y deserialización que toman la lista genérica como parámetro.

Echa un vistazo (funciona para mí):

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

¡Para que puedas serializar la lista que quieras! No necesita especificar el tipo de lista cada vez.

        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
fuente
3
Gracias por responder la pregunta. Agregaría que para List<MyClass>el elemento del documento debe nombrarse ArrayOfMyClass.
Max Toro
8

Sí, se deserializa a List <>. No es necesario mantenerlo en una matriz y envolverlo / encapsularlo en una lista.

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

    public UserHolder()
    {
    }

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

Código deserializador,

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

No estoy seguro acerca de List <T> pero las matrices son ciertamente factibles. Y un poco de magia hace que sea realmente fácil volver a una Lista.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
fuente
2
¿Es posible prescindir de la clase "titular"?
Daniel Schaffer
@Daniel, AFAIK, no. Necesita serializar y deserializar en algún tipo de objeto concreto. No creo que la serialización XML admita de forma nativa las clases de colección como el inicio de una serialización. Sin embargo, no lo sé al 100%.
JaredPar 03 de
[XmlElement ("list")] debería ser [XmlArray ("list")] en su lugar. Esa es la única forma en que la deserialización funcionó para mí en .NET 4.5
eduardobr
2

Qué tal si

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

No es particularmente elegante, pero debería funcionar.

PRJ
fuente
2
¡Bienvenido a stackoverflow! Siempre es mejor proporcionar una breve descripción de un código de muestra para mejorar la precisión de la publicación :)
Picrofo Software