168

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.

Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165

8 Answers8

154

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);
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 5
    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 or node (if you did not have the XmlElement attribute), and then add nodes under that. But I tried it and it did not, thus emitting exactly what the question wanted. – Jon Kragh Jul 26 '10 at 16:38
  • 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 types – Kala J Apr 24 '14 at 20:45
  • 2
    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. – DDRider62 Dec 31 '16 at 15:00
  • 1
    @DDRider62 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. – Marc Gravell Dec 31 '16 at 18:42
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.

Community
  • 1
  • 1
richaux
  • 2,622
  • 2
  • 35
  • 40
  • 16
    In my point of view, this is clearly THE answer to the question. The question was about deserializing into List. 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. – DDRider62 Dec 31 '16 at 14:59
  • 3
    With this approach, the `XmlSerializer` must be statically cached and reused to avoid a severe memory leak, see [Memory Leak using StreamReader and XmlSerializer](https://stackoverflow.com/q/23897145/3744182) for details. – dbc Sep 02 '18 at 15:48
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().

Coincoin
  • 27,880
  • 7
  • 55
  • 76
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);
tudor.iliescu
  • 323
  • 2
  • 4
  • 3
    Thanks for actually answering the question. I would add that for `List` the document element should be named `ArrayOfMyClass`. – Max Toro May 17 '12 at 03:50
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));
Nemo
  • 3,285
  • 1
  • 28
  • 22
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); } }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 2
    Is it possible to do without the "holder" class? – Daniel Schaffer Mar 03 '09 at 20:53
  • @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. – JaredPar Mar 03 '09 at 20:55
  • [XmlElement("list")] should be [XmlArray("list")] instead. That is the only way Deserialization worked for me in .NET 4.5 – eduardobr Mar 11 '16 at 15:59
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.

PRJ
  • 29
  • 1
-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.

Fred
  • 12,086
  • 7
  • 60
  • 83