Is it possible to deserialize XML into List<T>?
Asked Answered
M

8

169

Given the following 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>

And the following class:

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

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

Is it possible to use XmlSerializer to deserialize the xml into a List<User> ? If so, what type of additional attributes will I need to use, or what additional parameters do I need to use to construct the XmlSerializer instance?

An array ( User[] ) would be acceptable, if a bit less preferable.

Mule answered 3/3, 2009 at 20:47 Comment(0)
M
155

You can encapsulate the list trivially:

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);
    }
}
Morelli answered 3/3, 2009 at 21:5 Comment(4)
Nice solution with the [XmlElement("user")] to avoid an extra level of elements. Looking at this, I thought for sure that it would have emitted a <user> or <Items> node (if you did not have the XmlElement attribute), and then add <user> nodes under that. But I tried it and it did not, thus emitting exactly what the question wanted.Quietism
What if I had two lists under UserList above? I tried your method and it says it already defines a member called XYZ with the same parameter typesAeroplane
I don't know why this is marked as right answer. It includes adding a class to wrap the list. That was certainly what the question is trying to avoid.Helga
@Helga the question doesn't say "without wrapping". Most people are pretty pragmatic and just want to get the data out. This answer allows you to do that, via the .Items member.Morelli
C
61

If you decorate the User class with the XmlType to match the required capitalization:

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

Then the XmlRootAttribute on the XmlSerializer ctor can provide the desired root and allow direct reading into 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);
    }

Credit: based on answer from YK1.

Chasseur answered 14/2, 2014 at 11:23 Comment(2)
In my point of view, this is clearly THE answer to the question. The question was about deserializing into List<T>. All the other solutions, except maybe one, include a wrapping class to contain the list, which was certainly not the question posted, and what the author of the question seems to be trying to avoid.Helga
With this approach, the XmlSerializer must be statically cached and reused to avoid a severe memory leak, see Memory Leak using StreamReader and XmlSerializer for details.Judiejudith
L
18

Yes, it will serialize and deserialize a List<>. Just make sure you use the [XmlArray] attribute if in doubt.

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

This works with both Serialize() and Deserialize().

Lagniappe answered 3/3, 2009 at 21:5 Comment(1)
That's the magic piece I was missingSiret
U
15

I think I have found a better way. You don't have to put attributes into your classes. I've made two methods for serialization and deserialization which take generic list as parameter.

Take a look (it works for me):

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

So you can serialize whatever list you want! You don't need to specify the list type every time.

        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);
Uttermost answered 1/2, 2011 at 7:54 Comment(1)
Thanks for actually answering the question. I would add that for List<MyClass> the document element should be named ArrayOfMyClass.Mitosis
I
9

Yes, it does deserialize to List<>. No need to keep it in an array and wrap/encapsulate it in a list.

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

    public UserHolder()
    {
    }

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

Deserializing code,

XmlSerializer xs = new XmlSerializer(typeof(UserHolder));
UserHolder uh = (UserHolder)xs.Deserialize(new StringReader(str));
Itinerancy answered 22/10, 2009 at 15:33 Comment(0)
C
5

Not sure about List<T> but Arrays are certainly do-able. And a little bit of magic makes it really easy to get to a List again.

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

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}
Convery answered 3/3, 2009 at 20:51 Comment(3)
Is it possible to do without the "holder" class?Mule
@Daniel, AFAIK, no. You need to serialize and deserialize into some concrete object type. I do not believe that XML serialization natively supports collection classes as the start of a serialization. I do not 100% know that though.Convery
[XmlElement("list")] should be [XmlArray("list")] instead. That is the only way Deserialization worked for me in .NET 4.5Maggs
S
2

How about

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

Not particularly fancy but it should work.

Succursal answered 17/2, 2012 at 15:55 Comment(1)
Welcome to stackoverflow! It's always better to provide a short description for a sample code to improve the post accuracy :)Evangelicalism
P
-1

Yes, you can deserialize into List<User> or User[] with the XmlSerializer. I would prefer List<User> over User[]. Note that XmlSerializer does not support deserialization into interfaces, so you cannot deserialize into ICollection<T>, IReadOnlyCollection<T> or IList<T> as this will fail with a NotSupportedException.

Pneumatophore answered 26/1, 2023 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.