Apakah mungkin untuk deserialisasi XML ke dalam Daftar <T>?

155

Diberikan XML berikut:

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

Dan kelas berikut:

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

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

Apakah mungkin untuk menggunakan XmlSerializerdeserialize xml menjadi List<User>? Jika demikian, tipe atribut tambahan apa yang perlu saya gunakan, atau parameter tambahan apa yang perlu saya gunakan untuk membuat XmlSerializerinstance?

Array ( User[]) akan dapat diterima, jika sedikit kurang disukai.

Daniel Schaffer
sumber

Jawaban:

137

Anda dapat merangkum daftar ini secara sepele:

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
sumber
5
Solusi yang bagus dengan [XmlElement ("pengguna")] untuk menghindari elemen tingkat ekstra. Melihat ini, saya berpikir pasti bahwa itu akan memancarkan simpul <user> atau <Items> (jika Anda tidak memiliki atribut XmlElement), dan kemudian menambahkan node <user> di bawahnya. Tetapi saya mencobanya dan ternyata tidak, sehingga memancarkan apa yang diinginkan pertanyaan itu.
Jon Kragh
Bagaimana jika saya memiliki dua daftar di bawah UserList di atas? Saya mencoba metode Anda dan mengatakan sudah mendefinisikan anggota bernama XYZ dengan tipe parameter yang sama
Kala J
Saya tidak tahu mengapa ini ditandai sebagai jawaban yang benar. Ini termasuk menambahkan kelas untuk membungkus daftar. Itulah yang ingin dihindari oleh pertanyaan itu.
DDRider62
1
@ DDRider62 pertanyaannya tidak mengatakan "tanpa membungkus". Kebanyakan orang sangat pragmatis dan hanya ingin mengeluarkan data. Jawaban ini memungkinkan Anda melakukan itu, melalui .Itemsanggota.
Marc Gravell
50

Jika Anda mendekorasi Userkelas dengan XmlTypeagar sesuai dengan kapitalisasi yang diperlukan:

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

Maka XmlRootAttributepada XmlSerializerctor dapat memberikan root yang diinginkan dan memungkinkan pembacaan langsung ke Daftar <>:

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

Kredit: berdasarkan jawaban dari YK1 .

richaux
sumber
11
Dalam pandangan saya, ini jelas jawaban untuk pertanyaan itu. Pertanyaannya adalah tentang deserialisasi ke dalam Daftar <T>. Semua solusi lain, kecuali mungkin satu, termasuk kelas pembungkus untuk memuat daftar, yang tentunya bukan pertanyaan yang diposting, dan apa yang tampaknya ingin dihindari oleh penulis pertanyaan.
DDRider62
1
Dengan pendekatan ini, XmlSerializerharus di-cache secara statis dan digunakan kembali untuk menghindari kebocoran memori yang parah, lihat Memory Leak menggunakan StreamReader dan XmlSerializer untuk detailnya.
dbc
16

Ya, itu akan membuat serial dan membatalkan daftar Daftar <>. Pastikan Anda menggunakan atribut [XmlArray] jika ragu.

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

Ini berfungsi baik dengan Serialize () dan Deserialize ().

Coincoin
sumber
16

Saya pikir saya telah menemukan cara yang lebih baik. Anda tidak harus memasukkan atribut ke dalam kelas Anda. Saya telah membuat dua metode untuk serialisasi dan deserialization yang mengambil daftar generik sebagai parameter.

Lihatlah (ini bekerja untuk saya):

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

Jadi Anda bisa membuat serial daftar apa pun yang Anda inginkan! Anda tidak perlu menentukan jenis daftar setiap kali.

        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
sumber
3
Terima kasih telah benar-benar menjawab pertanyaan. Saya akan menambahkan bahwa untuk List<MyClass>elemen dokumen harus diberi nama ArrayOfMyClass.
Max Toro
8

Ya, itu menghapus daftar ke Daftar <>. Tidak perlu menyimpannya dalam array dan membungkus / merangkumnya dalam daftar.

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

    public UserHolder()
    {
    }

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

Kode deserializing,

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

Tidak yakin tentang Daftar <T> tetapi Array tentu saja bisa dilakukan. Dan sedikit keajaiban membuatnya sangat mudah untuk mencapai Daftar lagi.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
sumber
2
Apakah mungkin dilakukan tanpa kelas "pemegang"?
Daniel Schaffer
@Aniel, AFAIK, no. Anda perlu membuat cerita bersambung dan melakukan deserialisasi ke dalam beberapa jenis objek konkret. Saya tidak percaya bahwa serialisasi XML secara asli mendukung kelas koleksi sebagai awal dari serialisasi. Saya tidak 100% tahu itu.
JaredPar
[XmlElement ("list")] harus menjadi [XmlArray ("list")] sebagai gantinya. Itulah satu-satunya cara Deserialization bekerja untuk saya di .NET 4.5
eduardobr
2

Bagaimana tentang

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

Tidak terlalu mewah tetapi harus bekerja.

PRJ
sumber
2
Selamat datang di stackoverflow! Itu selalu lebih baik untuk memberikan deskripsi singkat untuk kode sampel untuk meningkatkan akurasi posting :)
Perangkat Lunak Picrofo