Question

I have a class PersonList

[XmlRoot("Persons")]
PersonList : List<Human>

when I serialize this to XML, by default it will produce something like this:

<Persons>
  <Human>...</Human>
  <Human>...</Human>
</Persons>

My question is what needs to be done in order to change element Human to Person in the output? so the output would be :

<Persons>
  <Person>...</Person>
  <Person>...</Person>
</Persons>

and, how to deserialize the above XML to the PersonList class object?

Per Nick's advice, Here is my testing code:

[XmlRoot("Persons")]
public class Persons : List<Human>
{

}

[XmlRoot("Person")]
public class Human
{
    public Human()
    {
    }

    public Human(string name)
    {
        Name = name;
    }

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

}

void TestXmlSerialize()
{
    Persons personList = new Persons();
    personList.Add(new Human("John"));
    personList.Add(new Human("Peter"));

    try
    {
        using (StringWriter writer = new StringWriter())
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Persons));
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;

            XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
            namespaces.Add(string.Empty, string.Empty);

            XmlWriter xmlWriter = XmlWriter.Create(writer, settings);
            serializer.Serialize(xmlWriter, personList, namespaces);

            Console.Out.WriteLine(writer.ToString());
        }
    }
    catch (Exception e)
    {
        Console.Out.WriteLine( e.ToString());
    }
}

The output of the testing code is:

<Persons>
  <Human>
    <Name>John</Name>
  </Human>
  <Human>
    <Name>Peter</Name>
  </Human>
</Persons>

As the output shows, the [XmlRoot("Person")] on Human does not change the tag to Person from Human.

Was it helpful?

Solution

I don't think there is a way for you to control the name of the generated array elements.

If you can however wrap the Persons collection inside another class you will then have complete control over the generated output using XmlArrayAttribute and XmlArrayItemAttribute.

If you cannot create this new class you can resort to implementing IXmlSerializable, but this is much more complex.

An example for the first alternative follows:

[XmlRoot("Context")]
public class Context
{
    public Context() { this.Persons = new Persons(); }

    [XmlArray("Persons")]
    [XmlArrayItem("Person")]
    public Persons Persons { get; set; }
}

public class Persons : List<Human> { }

public class Human
{
    public Human() { }
    public Human(string name) { Name = name; }
    public string Name { get; set; }
}

class Program
{
    public static void Main(string[] args)
    {
        Context ctx = new Context();
        ctx.Persons.Add(new Human("john"));
        ctx.Persons.Add(new Human("jane"));

        var writer = new StringWriter();
        new XmlSerializer(typeof(Context)).Serialize(writer, ctx);

        Console.WriteLine(writer.ToString());
    }
}

OTHER TIPS

Mark your class with the following attributes:

[XmlType("Account")]
[XmlRoot("Account")]

I had the identical problem with my serializer. None of the answers above worked exactly. I found that the XmlRoot attribute on the Human class is plainly ignored because it isn't the root element of the document. Wrapping the list in a context object wasn't an option for me because I can't change the XML schema. The solution is to change up the Persons class. Instead of subclassing a generic list, you wrap it in an object and change how it is serialized. See the sample code below:

[XmlRoot("Persons")]
public class Persons 
{
    public Persons ()
    {
        People = new List<Human>();
    }

    [XmlElement("Person")]
    public List<Human> People 
    { get; set; }
}

public class Human
{
    public Human()
    {
    }

    public Human(string name)
    {
        Name = name;
    }

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

Serializing your generic list using XmlElement means that it won't put the wrapper element around your list like XmlArray does or like the subclassing does. It also gives you the bonus option of adding attributes to the Persons class, which is where I got the idea from:

How do I add a attribute to a XmlArray element (XML Serialization)?

This is mine test code

using System.Collections.Generic;
using System.Xml.Serialization;

namespace TestLoadingMultiXml
{
[XmlRoot(ElementName=@"main")]
public class XmlMain
{
    private XmlDataTest data;

    [XmlElement(ElementName=@"datalist")]
    public XmlDataTest Data
    {
        get { return data; }
        set { data = value; }
    } // public XmlDataTest Data

    public XmlMain()
    {
        data = new XmlDataTest();
    }
}

[XmlRoot(ElementName=@"xmldata")]
public class XmlDataTest
{
    private List<DataDetails> listData;

    [XmlElement(ElementName=@"listdata")]
    public List<DataDetails> Data
    {
        get { return listData; }
        set { listData = value; }
    }

    public XmlDataTest()
    {
        listData = new List<DataDetails>();
        for (int i = 0; i < 10; i++)
        {
            DataDetails d = new DataDetails(string.Format("{0}", i));
            listData.Add(d);
        } // for (int i=0; i < 10; i++)
    } // public XmlDataTest()
} // class XmlDataTest

[XmlRoot(ElementName=@"datadetail")]
public class DataDetails
{
    private string name;

    [XmlAttribute(AttributeName=@"name")]
    public string Name
    {
        get
        {
            return name;
        }
        set { name = value; }
    }

    public DataDetails(string _value)
    {
        this.name = _value;
    } // public DataDetails(string _value)

    public DataDetails()
    {
        this.name = "";
    } // public DataDetails()
} // public class DataDetails
}

and running program

using System;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace TestLoadingMultiXml
{
    public partial class Form1 : Form
    {
    private XmlMain xt;
    private string xname = @"x.xml";

    public Form1()
    {
        InitializeComponent();
        this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
    }

    void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        XmlSerializer x = new XmlSerializer(typeof(XmlMain));
        FileStream fs = new FileStream(xname, FileMode.Create);
        x.Serialize(fs, xt);
        fs.Close();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        xt = new XmlMain();
        xname = Directory.GetCurrentDirectory() + @"\" + xname;
        if (File.Exists(xname))
        {
            XmlSerializer x = new XmlSerializer(typeof(XmlMain));
            FileStream fs = new FileStream(xname, FileMode.Open);
            xt = (XmlMain)x.Deserialize(fs);
            fs.Close();
        } // if (File.Exists(xname))
    }
}
}

Also this is the result

<?xml version="1.0"?>
<main xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <datalist>
    <listdata name="0" />
    <listdata name="1" />
    <listdata name="2" />
    <listdata name="3" />
    <listdata name="4" />
    <listdata name="5" />
    <listdata name="6" />
    <listdata name="7" />
    <listdata name="8" />
    <listdata name="9" />
  </datalist>
</main>

Set the XmlRoot on Human to:

[XmlRoot("Person")]

Sidebar:

Persons should probably be People

There is another alternative. You can always implement IXmlSerializable in collections (and any other class or struct) to fully control how items are written or read. Many of you will know that already, it's not generally preferred of course as you may end-up writing out "boiler-plate" code by hand which should really be automatic logic specified with attributes.

For collections which must match a sensible schema it's justifiable. Because the framework has a hard limitation here and the existing item type's serialization code does not have to be duplicated when done properly; i.e. Do not re-write the item serialization in the collection code, just create/call a child XmlSerializer inside your ReadXml/WriteXml implementation.

Once consequence of using IXmlSerializable is it does not allow you to apply the XmlTypeAttribute (throws a runtime error telling you only XmlRootAttribute may be used). So instead apply the XmlSchemaProviderAttribute and return the same qualified name you would have put in the XmlTypeAttribute. The old GetSchema method should return null anyway as it was only a reserved method (according to MSDN), probably because they forgot to include the ability to specify a different namespace. Personally I use the same "GetSchema" method name in my XmlSchemaProviderAttribute so it appears as a complete override next to the legacy placeholder GetSchema method.

Of course, the best solution would be if Microsoft would allow us to apply the XmlArrayItemAttribute to collection/list classes and use that in the XmlSerializer. By default it uses the XML type element name in collections, which I feel is a bug because it should be the XML root name when specified or class name when not.

When I get time I'll come back and add an example. For now take a look at the MSDN documentation examples of IXmlSerializable and XmlSchemaProviderAttribute.

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx

http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlschemaproviderattribute(v=vs.110).aspx

If you don't have access to the source for the Human class (in which case, setting XmlRoot is not possible), you can create an XmlElementAttribute, then add it to an XmlAttributeOverride and use that when creating an instance of your XmlSerializer. See this MSDN article for more details.

I know it's an old question but I ran into the same problem and none of the solutions seems to adresse the OP's question. So here is my solution (comments are in french if you wonder) :

#region Références
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
#endregion

namespace XmlSerializationTests
{
    /// <summary>
    /// Représente une liste qui peut être sérialisée en XML en tant que noeud racine.
    /// </summary>
    /// <typeparam name="T">Type des éléments de la liste.</typeparam>
    public class XmlSerializableList<T>
        : List<T>, IXmlSerializable
    {
        #region Variables
        private static readonly XmlSerializer _ItemSerializer = new XmlSerializer(typeof(T));
        private static readonly string _ItemName;
        private string _RootName;
        #endregion

        #region Méthodes
        /// <summary>
        /// Initialisation statique
        /// </summary>
        static XmlSerializableList()
        {
            _ItemName = (typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? typeof(T).Name;
        }

        /// <summary>
        /// Obtient le nom racine.
        /// </summary>
        protected virtual string RootName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(_RootName)) _RootName = (GetType().GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? GetType().Name;
                return _RootName;
            }
        }

        /// <summary>
        /// Obtient le nom des éléments.
        /// </summary>
        protected virtual string ItemName
        {
            get { return _ItemName; }
        }

        /// <summary>
        /// Cette méthode est réservée et ne doit pas être utilisée.Lorsque vous implémentez l'interface IXmlSerializable, vous devez retourner la valeur null (Nothing dans Visual Basic) à partir cette méthode et, si la spécification d'un schéma personnalisé est requise, appliquez à la place <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> à la classe.
        /// </summary>
        /// <returns> <see cref="T:System.Xml.Schema.XmlSchema"/> qui décrit la représentation XML de l'objet qui est généré par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> et utilisé par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>.</returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Génère un objet à partir de sa représentation XML.
        /// </summary>
        /// <param name="reader"><see cref="T:System.Xml.XmlReader"/> source à partir de laquelle l'objet est désérialisé.</param>
        public void ReadXml(XmlReader reader)
        {
            if (!reader.IsEmptyElement)
            {
                reader.ReadStartElement();
                while (reader.NodeType != XmlNodeType.EndElement)
                {
                    T item = (T) _ItemSerializer.Deserialize(reader);
                    Add(item);
                }
                reader.ReadEndElement();
            }
            else reader.ReadStartElement();
        }

        /// <summary>
        /// Convertit un objet en sa représentation XML.
        /// </summary>
        /// <param name="writer"><see cref="T:System.Xml.XmlWriter"/> flux dans lequel l'objet est sérialisé.</param>
        public void WriteXml(XmlWriter writer)
        {
            foreach (var i in this)
            {
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("", "");
                _ItemSerializer.Serialize(writer, i, ns);
            }
        }
        #endregion
    }
}

And here a unit test class to demonstrate use and results :

#region Références
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endregion

namespace XmlSerializationTests
{
    [TestClass]
    public class XmlSerializableListTests
    {
        public class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int Birth { get; set; }
        }

        [XmlRoot("color")]
        public class ColorDefinition
        {
            [XmlElement("name")] public string Name { get; set; }
            [XmlElement("r")] public int Red { get; set; }
            [XmlElement("g")] public int Green { get; set; }
            [XmlElement("b")] public int Blue { get; set; }
        }

        public class Persons : XmlSerializableList<Person>
        {
        }

        [XmlRoot("colors")]
        public class ColorList : XmlSerializableList<ColorDefinition>
        {
        }

        private T ReadXml<T>(string text) where T : class
        {
            XmlSerializer serializer = new XmlSerializer(typeof (T));
            using (StringReader sr = new StringReader(text))
            {
                return serializer.Deserialize(sr) as T;
            }
        }

        private string WriteXml<T>(T data) where T : class
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            {
                serializer.Serialize(sw, data);
                return sb.ToString();
            }
        }

        [TestMethod]
        public void ReadEmpty()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(0, lst.Count);
        }

        [TestMethod]
        public void ReadEmpty2()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(0, lst.Count);
        }

        [TestMethod]
        public void ReadSimpleItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
  <int>0</int>
  <int>52</int>
  <int>79</int>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual(0, lst[0]);
            Assert.AreEqual(52, lst[1]);
            Assert.AreEqual(79, lst[2]);
        }

        [TestMethod]
        public void ReadComplexItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</XmlSerializableListOfPerson>";
            XmlSerializableList<Person> lst = ReadXml<XmlSerializableList<Person>>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("Linus", lst[0].FirstName);
            Assert.AreEqual("Torvalds", lst[0].LastName);
            Assert.AreEqual(1969, lst[0].Birth);
            Assert.AreEqual("Bill", lst[1].FirstName);
            Assert.AreEqual("Gates", lst[1].LastName);
            Assert.AreEqual(1955, lst[1].Birth);
            Assert.AreEqual("Steve", lst[2].FirstName);
            Assert.AreEqual("Jobs", lst[2].LastName);
            Assert.AreEqual(1955, lst[2].Birth);
        }

        [TestMethod]
        public void ReadInheritedPersons()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</Persons>";
            Persons lst = ReadXml<Persons>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("Linus", lst[0].FirstName);
            Assert.AreEqual("Torvalds", lst[0].LastName);
            Assert.AreEqual(1969, lst[0].Birth);
            Assert.AreEqual("Bill", lst[1].FirstName);
            Assert.AreEqual("Gates", lst[1].LastName);
            Assert.AreEqual(1955, lst[1].Birth);
            Assert.AreEqual("Steve", lst[2].FirstName);
            Assert.AreEqual("Jobs", lst[2].LastName);
            Assert.AreEqual(1955, lst[2].Birth);
        }

        [TestMethod]
        public void ReadInheritedColors()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
  <color>
    <name>red</name>
    <r>255</r>
    <g>0</g>
    <b>0</b>
  </color>
  <color>
    <name>green</name>
    <r>0</r>
    <g>255</g>
    <b>0</b>
  </color>
  <color>
    <name>yellow</name>
    <r>255</r>
    <g>255</g>
    <b>0</b>
  </color>
</colors>";
            ColorList lst = ReadXml<ColorList>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("red", lst[0].Name);
            Assert.AreEqual(255, lst[0].Red);
            Assert.AreEqual(0, lst[0].Green);
            Assert.AreEqual(0, lst[0].Blue);
            Assert.AreEqual("green", lst[1].Name);
            Assert.AreEqual(0, lst[1].Red);
            Assert.AreEqual(255, lst[1].Green);
            Assert.AreEqual(0, lst[1].Blue);
            Assert.AreEqual("yellow", lst[2].Name);
            Assert.AreEqual(255, lst[2].Red);
            Assert.AreEqual(255, lst[2].Green);
            Assert.AreEqual(0, lst[2].Blue);
        }

        [TestMethod]
        public void WriteEmpty()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
            XmlSerializableList<int> lst = new XmlSerializableList<int>();
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteSimpleItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
  <int>0</int>
  <int>52</int>
  <int>79</int>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = new XmlSerializableList<int>() {0, 52, 79};
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteComplexItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</XmlSerializableListOfPerson>";
            XmlSerializableList<Person> persons = new XmlSerializableList<Person>
            {
                new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
                new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
                new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
            };
            string result = WriteXml(persons);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteInheritedPersons()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</Persons>";
            Persons lst = new Persons
            {
                new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
                new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
                new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
            };
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteInheritedColors()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
  <color>
    <name>red</name>
    <r>255</r>
    <g>0</g>
    <b>0</b>
  </color>
  <color>
    <name>green</name>
    <r>0</r>
    <g>255</g>
    <b>0</b>
  </color>
  <color>
    <name>yellow</name>
    <r>255</r>
    <g>255</g>
    <b>0</b>
  </color>
</colors>";
            ColorList lst = new ColorList
            {
                new ColorDefinition { Name = "red", Red = 255, Green = 0, Blue = 0 },
                new ColorDefinition { Name = "green", Red = 0, Green = 255, Blue = 0 },
                new ColorDefinition { Name = "yellow", Red = 255, Green = 255, Blue = 0 }
            };
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top