Можно ли десериализовать XML в List <T>?

155

Учитывая следующий 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>

И следующий класс:

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

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

Можно ли использовать XmlSerializerдля десериализации XML в List<User>? Если да, какой тип дополнительных атрибутов мне нужно использовать, или какие дополнительные параметры мне нужно использовать для создания XmlSerializerэкземпляра?

Массив ( User[]) будет приемлемым, если он немного менее предпочтителен.

Даниэль Шаффер
источник

Ответы:

137

Вы можете инкапсулировать список тривиально:

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);
    }
}
Марк Гравелл
источник
5
Хорошее решение с [XmlElement ("пользователь")], чтобы избежать дополнительного уровня элементов. Глядя на это, я подумал наверняка, что он бы испустил узел <user> или <Items> (если у вас не было атрибута XmlElement), а затем добавил узлы <user> под этим. Но я попробовал, а он этого не сделал, испуская именно то, что хотел вопрос.
Джон Краг
Что если бы у меня было два списка в UserList выше? Я попробовал ваш метод, и он говорит, что он уже определяет член с именем XYZ с теми же типами параметров
Kala J
Я не знаю, почему это помечено как правильный ответ. Включает добавление класса для переноса списка. Это было, конечно, то, что вопрос пытается избежать.
DDRider62
1
@ DDRider62 вопрос не говорит "без упаковки". Большинство людей довольно прагматичны и просто хотят получить данные. Этот ответ позволяет вам сделать это через .Itemsучастника.
Марк Гравелл
50

Если вы украсите Userкласс, XmlTypeчтобы он соответствовал требуемой заглавной букве:

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

Тогда XmlRootAttributeна XmlSerializerCTOR может обеспечить нужный корень и позволяет прямое чтение в 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);
    }

Кредит: основано на ответе от YK1 .

richaux
источник
11
На мой взгляд, это явно ответ на вопрос. Вопрос был о десериализации в List <T>. Все другие решения, за исключением, может быть, одного, включают в себя класс-обертку для хранения списка, который, конечно, не был опубликован в вопросе, и что автор вопроса, похоже, пытается избежать.
DDRider62
1
При таком подходе XmlSerializerнеобходимо статически кэшировать и повторно использовать, чтобы избежать серьезной утечки памяти, см. Подробности в разделе Утечка памяти с использованием StreamReader и XmlSerializer .
декабря
16

Да, он будет сериализовать и десериализовать список <>. Просто убедитесь, что вы используете атрибут [XmlArray], если сомневаетесь.

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

Это работает как с Serialize (), так и с Deserialize ().

Coincoin
источник
16

Я думаю, что нашел лучший способ. Вам не нужно помещать атрибуты в ваши классы. Я сделал два метода для сериализации и десериализации, которые принимают общий список в качестве параметра.

Посмотрите (это работает для меня):

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

Таким образом, вы можете сериализовать любой список, который вы хотите! Вам не нужно указывать тип списка каждый раз.

        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
источник
3
Спасибо за ответ на вопрос. Я бы добавил, что для List<MyClass>документа должен быть указан элемент ArrayOfMyClass.
Макс Торо
8

Да, он десериализуется в список <>. Не нужно хранить его в массиве и оборачивать / инкапсулировать в список.

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

    public UserHolder()
    {
    }

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

Десериализация кода,

XmlSerializer xs = new XmlSerializer(typeof(UserHolder));
UserHolder uh = (UserHolder)xs.Deserialize(new StringReader(str));
Nemo
источник
5

Не уверен насчет List <T>, но массивы, безусловно, выполнимы. И немного магии позволяет действительно легко снова попасть в Список.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
JaredPar
источник
2
Можно ли обойтись без класса «держатель»?
Даниэль Шаффер
@Daniel, AFAIK, нет. Вам нужно сериализовать и десериализовать в какой-то конкретный тип объекта. Я не верю, что XML-сериализация изначально поддерживает классы коллекций как начало сериализации. Я не знаю на 100%, хотя.
JaredPar
Вместо этого [XmlElement ("список")] должен быть [XmlArray ("список")]. Это единственный способ, которым десериализация работала для меня в .NET 4.5
eduardobr
2

Как насчет

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

Не особенно необычно, но это должно работать.

PRJ
источник
2
Добро пожаловать в stackoverflow! Всегда лучше предоставить краткое описание примера кода, чтобы повысить точность публикации :)
Picrofo Software