.NET serialization of Array to XML. How to set an alias for array type?

10,165

Solution 1

You just need to make some small changes to your code, in addition to the suggestions already provided.

First the SerializeObject generic method needs to be redeclared thus:

// important: declare the input parameter to be an **array** of T, not T.
static void SerializeObject<T>(T[] obj) where T : class
{
    string fileName = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
    using (FileStream fs = File.Create(fileName))
    {
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", "");

        // override default root node name.  based on your question, 
        // i'm just going to append an "s" to the base type 
        // (e.g., Person becomes Persons)
        var rootName = typeof(T).Name + "s";
        XmlRootAttribute root = new XmlRootAttribute(rootName);

        // add the attribute to the serializer constructor...
        XmlSerializer ser = new XmlSerializer(obj.GetType(), root);

        ser.Serialize(fs, obj, ns);
    }
}

Secondly, in the Main() method, replace SerializeObject<Person[]>(p) with SerializeObject<Person>(p). Thus your Main() method will look like this:

static void Main(string[] args)
{
    Person[] p = 
    {
        new Person{Age = 20, Firstname = "Michael", Lastname = "Jackson"},
        new Person{Age = 21, Firstname = "Bill", Lastname = "Gates"},
        new Person{Age = 22, Firstname = "Steve", Lastname = "Jobs"}
    };

    SerializeObject<Person>(p);
}

The resulting XML will look like this:

<Persons>
  <Person>
    <Firstname>Michael</Firstname>
    <Lastname>Jackson</Lastname>
    <Age>20</Age>
  </Person>
  <Person>
    <Firstname>Bill</Firstname>
    <Lastname>Gates</Lastname>
    <Age>21</Age>
  </Person>
  <Person>
    <Firstname>Steve</Firstname>
    <Lastname>Jobs</Lastname>
    <Age>22</Age>
  </Person>
</Persons>

To override the <Person> element name to something else, set the XmlType attribute on the class, like so:

[XmlType("personEntry")]
public class Person
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public uint Age { get; set; }
}

The resulting XML looks like this:

<Persons>
  <personEntry>
    <Firstname>Michael</Firstname>
    <Lastname>Jackson</Lastname>
    <Age>20</Age>
  </personEntry>
  <personEntry>
    <Firstname>Bill</Firstname>
    <Lastname>Gates</Lastname>
    <Age>21</Age>
  </personEntry>
  <personEntry>
    <Firstname>Steve</Firstname>
    <Lastname>Jobs</Lastname>
    <Age>22</Age>
  </personEntry>
</Persons>

Solution 2

You need a container class like so:

    /// <summary>
    /// Represents an Person collection.
    /// </summary>
    [Serializable]
    [XmlRoot("Persons", IsNullable = false)]
    public sealed class Persons
    {
        /// <summary>
        /// The person collection.
        /// </summary>
        private Collection<Person> persons;

        /// <summary>
        /// Initializes a new instance of the <see cref="Persons"/> class.
        /// </summary>
        /// <param name="persons">The person list.</param>
        public Persons(Collection<Person> persons)
        {
            this.persons = persons;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Persons"/> class.
        /// </summary>
        /// <param name="persons">The person array.</param>
        public Persons(Person[] persons)
            : this(new Collection<Person>(persons))
        {
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="Persons"/> class from being created.
        /// </summary>
        private Persons()
        {
        }

        /// <summary>
        /// Copies the collection of Person objects to an array and returns
        /// it.
        /// </summary>
        /// <returns>An array of Person objects based on the
        /// collection.</returns>
        public Person[] ToArray()
        {
            Person[] personArray = new Person[this.persons.Count];

            this.persons.CopyTo(personArray, 0);
            return personArray;
        }

        /// <summary>
        /// Gets or sets the persons.
        /// </summary>
        /// <value>The persons.</value>
        [XmlElement("Person")]
        public Collection<Person> ThePersons
        {
            get
            {
                return this.persons;
            }

            set
            {
                this.persons = value;
            }
        }

        /// <summary>
        /// Gets the length of the persons.
        /// </summary>
        /// <value>The length of the persons.</value>
        [XmlIgnore]
        public int Length
        {
            get
            {
                return this.persons.Count;
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>A <see cref="IEnumerator&lt;Person&gt;"/> that can be used to
        /// iterate through the collection.</returns>
        public IEnumerator<Person> GetEnumerator()
        {
            return (IEnumerator<Person>)this.persons.GetEnumerator();
        }
    }

Initialize it with your completed array and return it as per your Main() method:

    static void Main ()
    {
        Person[] p = 
        {
            new Person{Age = 20, Firstname = "Michael", Lastname = "Jackson"},
            new Person{Age = 21, Firstname = "Bill", Lastname = "Gates"},
            new Person{Age = 22, Firstname = "Steve", Lastname = "Jobs"}
        };

        SerializeObject<Persons>(new Persons(p));

        Person[] p2 = DeserializeObject<Persons>("filename.xml").ToArray();
    }

The deserializer method is pretty simple then:

    static T DeserializeObject<T>(string fileName) where T : class
    {
        using (FileStream fs = File.OpenRead(fileName))
        {
            XmlSerializer ser = new XmlSerializer(typeof(T));
            return (T)ser.Deserialize(fs);
        }
    }

Option 2 (building on Nix's answer):

    static void SerializeObject<T>(T obj, Type t) where T : class
    {
        string fileName = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
        using (FileStream fs = File.Create(fileName))
        {
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
            XmlRootAttribute root = new XmlRootAttribute(t.Name + "s");
            XmlSerializer ser = new XmlSerializer(typeof(T), root);
            ser.Serialize(fs, obj, ns);
        }
    }

can be called as such:

        SerializeObject<Person[]>(p, typeof(Person));

Solution 3

You can do it by adding a root attribute to your serializer. See below.

 static void SerializeObject<T>(T obj) where T : class
    {
        string fileName = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
        using (FileStream fs = File.Create(fileName)) 
        {
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");

            XmlRootAttribute root = new XmlRootAttribute( typeof(T).Name + "s");

            XmlSerializer
              ser = new XmlSerializer(typeof(Person[]), root);
              ser.Serialize(fs, obj, ns);
        }
    }

Alternatively you could pass in a func that does the name selecting. Your code would be

 SerializeObject<Person[]>(p, per=>p.GetType().Name);



static void SerializeObject<T>(T obj, Func<T,string> nameSelector) where T : class
{
    string fileName = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
    using (FileStream fs = File.Create(fileName))
    {
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", "");

        XmlRootAttribute root = new XmlRootAttribute(nameSelector(obj));

        XmlSerializer
          ser = new XmlSerializer(typeof(Person[]), root);
        ser.Serialize(fs, obj, ns);
    }
}
Share:
10,165
the_V
Author by

the_V

Updated on June 07, 2022

Comments

  • the_V
    the_V almost 2 years

    I'm trying to figure out serialization of .net arrays to XML. Here's a piece of code that I've come up with:

        public class Program
        {
            public class Person 
            {
                public string Firstname { get; set; }
                public string Lastname { get; set; }
                public uint Age { get; set; }
            }
    
            static void Main ()
            {
                Person[] p = 
                {
                    new Person{Age = 20, Firstname = "Michael", Lastname = "Jackson"},
                    new Person{Age = 21, Firstname = "Bill", Lastname = "Gates"},
                    new Person{Age = 22, Firstname = "Steve", Lastname = "Jobs"}
                };
    
                SerializeObject<Person[]>(p);
            }
    
            static void SerializeObject<T>(T obj) where T : class
            {
                string fileName = Guid.NewGuid().ToString().Replace("-", "") + ".xml";
                using (FileStream fs = File.Create(fileName)) 
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    XmlSerializer ser = new XmlSerializer(typeof(T));
                    ser.Serialize(fs, obj, ns);
                }
            }
        }
    

    Here's an XML content that this example writes down to the XML file:

    <ArrayOfPerson>
      <Person>
        <Firstname>Michael</Firstname>
        <Lastname>Jackson</Lastname>
        <Age>20</Age>
      </Person>
      <Person>
        <Firstname>Bill</Firstname>
        <Lastname>Gates</Lastname>
        <Age>21</Age>
      </Person>
      <Person>
        <Firstname>Steve</Firstname>
        <Lastname>Jobs</Lastname>
        <Age>22</Age>
      </Person>
    </ArrayOfPerson>
    

    But this is not really what I want. I would like it to look like this:

    <Persons>
      <Person>
        <Firstname>Michael</Firstname>
        <Lastname>Jackson</Lastname>
        <Age>20</Age>
      </Person>
      <Person>
        <Firstname>Bill</Firstname>
        <Lastname>Gates</Lastname>
        <Age>21</Age>
      </Person>
      <Person>
        <Firstname>Steve</Firstname>
        <Lastname>Jobs</Lastname>
        <Age>22</Age>
      </Person>
    </Persons>
    

    How could I get it working this way? Thanks in advance!