When a Class Is Inherited from List<>, Xmlserializer Doesn't Serialize Other Attributes

When a class is inherited from List, XmlSerializer doesn't serialize other attributes

This is by design. I don't know why this decision was made, but it is stated in the documentation:

  • Classes that implement ICollection or IEnumerable. Only collections are
    serialized, not public properties.

(Look under "Items that can be serialized" section). Someone has filed a bug against this, but it won't be changed - here, Microsoft also confirms that not including the properties for classes implementing ICollection is in fact the behaviour of XmlSerializer.

A workaround would be to either:

  • Implement IXmlSerializable and control serialization yourself.

or

  • Change MyClass so it has a public property of type List (and don't subclass it).

or

  • Use DataContractSerializer, which handles this scenario.

Serializing class inherited from List using DataContractSerializer does not serialize object properties

I tried to get an inherited class List to work and was not successful. This is the best I could do

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApplication106
{

class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XSerBase test = new XSerBase()
{
XSerTest = new List<XSerTest>() {
new XSerTest() { TestValue = "123"},
new XSerTest() { TestValue = "456"}
}
};

XmlSerializer serializer = new XmlSerializer(typeof(XSerBase));

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(FILENAME,settings);

serializer.Serialize(writer, test);

writer.Flush();
writer.Close();

}

}
public class XSerBase
{
[XmlElement("XSerTest")]
public List<XSerTest> XSerTest { get; set; }
}
public class XSerTest
{
public string TestValue { get; set; }
}

}

Lost attributes in serialization List inherited class

If you change the class it will serialize the name field too.

public class cIntList
{
public string Name{ get; set; }

[XmlElement("")]
public List<IntData> IntList{ get; set; }

public cIntList()
{
Name = "Name";
IntList = new List<IntData>();
IntList.Add(new IntData() { Value = 1 });
IntList.Add(new IntData() { Value = 2 });
}
}

You can change or remove the XmlElement attribute depending on your desired xml.

Serialize object when the object inherits from list

According to the comments above (thanks!) there are two ways to get a correct result:

  • Implementing a custom JsonConverter (see here)
  • Workarround: Create a property in the class which returns the items (see here)

Anyway, inherit from List<T> is rare to be a good solution (see here)

I've tried it with the workarround:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class A : List<B>
{
[JsonProperty]
public double TestA { get; set; }

[JsonProperty]
public B[] Items
{
get
{
return this.ToArray();
}
set
{
if (value != null)
this.AddRange(value);
}
}
}

public class B
{
public double TestB { get; set; }
}

This works for serialization and deserialization. Important: Items must be an Array of B and no List<B>. Otherwise deserialization doesn't work for Items.

Serialize a list of objects inheriting from class A to xml, so the names of the elements in the xml are B,C,D

Firstly DataContractSerializer does not have a mechanism to support collection item polymorphism by changing collection element name(s). It only supports the known type mechanism which uses the i:type attribute - which you indicate is not acceptable.

Since you are willing to switch to XmlSerializer, you could use the attribute XmlArrayItemAttribute.Type to specify element names for polymorphic types in lists:

public class AListObject
{
[XmlArrayItem(typeof(B))]
[XmlArrayItem(typeof(C))]
public List<A> SerializedListObjects { get; set; }
}

However, you also indicate that the polymorphic subtypes cannot be declared statically at compile type because they exist in some other assembly.

As a result, you will need to use the XmlAttributeOverrides mechanism to specify all possible derived types for all List<A> properties in runtime, and manually construct an XmlSerializer using those overrides.

Here is a prototype solution. First, let's assume you have a root object that refers to an object containing a List<A> like so:

public class RootObject
{
public AListObject AList { get; set; }
}

public class AListObject
{
public List<A> SerializedListObjects { get; set; }
}

(The root object could be the object with the List<A> property, but doesn't need to be.) Let's also assume you know all such objects like AListObject that may contain List<A> properties.

With those assumptions, the following serializer factory can be used to generate an XmlSerializer for any root object that may refer to any instances of the known types containing a List<A> property:

public interface IXmlSerializerFactory
{
XmlSerializer CreateSerializer(Type rootType);
}

public static class AListSerializerFactory
{
static readonly XmlArrayItemTypeOverrideSerializerFactory instance;

static AListSerializerFactory()
{
// Include here a list of all types that have a List<A> property.
// You could use reflection to find all such public types in your assemblies.
var declaringTypeList = new []
{
typeof(AListObject),
};

// Include here a list of all base types with a corresponding mapping
// to find all derived types in runtime. Here you could use reflection
// to find all such types in your assemblies, as shown in
// https://stackoverflow.com/questions/857705/get-all-derived-types-of-a-type
var derivedTypesList = new Dictionary<Type, Func<IEnumerable<Type>>>
{
{ typeof(A), () => new [] { typeof(B), typeof(C) } },
};
instance = new XmlArrayItemTypeOverrideSerializerFactory(declaringTypeList, derivedTypesList);
}

public static IXmlSerializerFactory Instance { get { return instance; } }
}

public class XmlArrayItemTypeOverrideSerializerFactory : IXmlSerializerFactory
{
// To avoid a memory & resource leak, the serializers must be cached as explained in
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer

readonly object padlock = new object();
readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
readonly XmlAttributeOverrides overrides;

public XmlArrayItemTypeOverrideSerializerFactory(IEnumerable<Type> declaringTypeList, IEnumerable<KeyValuePair<Type, Func<IEnumerable<Type>>>> derivedTypesList)
{
var completed = new HashSet<Type>();
overrides = declaringTypeList
.SelectMany(d => derivedTypesList.Select(p => new { declaringType = d, itemType = p.Key, derivedTypes = p.Value() }))
.Aggregate(new XmlAttributeOverrides(), (a, d) => a.AddXmlArrayItemTypes(d.declaringType, d.itemType, d.derivedTypes, completed));
}

public XmlSerializer CreateSerializer(Type rootType)
{
lock (padlock)
{
XmlSerializer serializer;
if (!serializers.TryGetValue(rootType, out serializer))
serializers[rootType] = serializer = new XmlSerializer(rootType, overrides);
return serializer;
}
}
}

public static partial class XmlAttributeOverridesExtensions
{
public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes)
{
return overrides.AddXmlArrayItemTypes(declaringType, itemType, derivedTypes, new HashSet<Type>());
}

public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes, HashSet<Type> completedTypes)
{
if (overrides == null || declaringType == null || itemType == null || derivedTypes == null || completedTypes == null)
throw new ArgumentNullException();
XmlAttributes attributes = null;
for (; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
{
// Avoid duplicate overrides.
if (!completedTypes.Add(declaringType))
break;
foreach (var property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
{
// Skip the property if already ignored
if (property.IsDefined(typeof(XmlIgnoreAttribute), false))
continue;

// See if it is a list property, and if so, get its type.
var propertyItemType = property.PropertyType.GetListType();
if (propertyItemType == null)
continue;

// OK, its a List<itemType>. Add all the necessary XmlElementAttribute declarations.
if (propertyItemType == itemType)
{
if (attributes == null)
{
attributes = new XmlAttributes();
foreach (var derivedType in derivedTypes)
// Here we are assuming all the derived types have unique XML type names.
attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = derivedType });
if (itemType.IsConcreteType())
attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = itemType });
}
overrides.Add(declaringType, property.Name, attributes);
}
}
}
return overrides;
}
}

public static class TypeExtensions
{
public static bool IsConcreteType(this Type type)
{
return !type.IsAbstract && !type.IsInterface;
}

public static Type GetListType(this Type type)
{
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}

Then, you can serialize and deserialize instances of RootObject to and from XML as follows:

var root = new RootObject
{
AList = new AListObject
{
SerializedListObjects = new List<A> { new B(), new C() },
},
};

var serializer = AListSerializerFactory.Instance.CreateSerializer(root.GetType());

var xml = root.GetXml(serializer);
var root2 = xml.LoadFromXml<RootObject>(serializer);

Using the extension methods:

public static class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString, XmlSerializer serial = null)
{
serial = serial ?? new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T)serial.Deserialize(reader);
}
}

public static string GetXml<T>(this T obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj);
return textWriter.ToString();
}
}
}

And the result is:

<RootObject xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AList>
<SerializedListObjects>
<B />
<C />
</SerializedListObjects>
</AList>
</RootObject>

Notes:

  • As explained in Memory Leak using StreamReader and XmlSerializer, you must statically cache any XmlSerializer constructed with XmlAttributeOverrides to avoid a severe memory leak. The documentation suggests using a Hashtable, however XmlAttributeOverrides does not override Equals() or GetHashCode(), and does not provide enough access to its internal data for applications developers to write their own. Thus it's necessary to hand-craft some sort of static caching scheme whenever XmlAttributeOverrides is used.

  • Given the complexity of finding and overriding the XmlArrayItem attributes of all List<A> properties, you might consider sticking with the existing i:type mechanism. It's simple, works well, is supported by both DataContractSerializer and XmlSerializer, and is standard.

  • I wrote the class XmlArrayItemTypeOverrideSerializerFactory in a generic way, which adds to the apparent complexity.

Working sample .Net fiddle here.

Using XmlSerializer to serialize derived classes

There are three ways of doing this; either you can use [XmlInclude] against the type, or you can use XmlElement/XmlArrayItem against the property. They are all shown below; uncomment the pair you prefer:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
public class MyWrapper {
//2: [XmlElement("A", Type = typeof(ChildA))]
//2: [XmlElement("B", Type = typeof(ChildB))]
//3: [XmlArrayItem("A", Type = typeof(ChildA))]
//3: [XmlArrayItem("B", Type = typeof(ChildB))]
public List<ChildClass> Data { get; set; }
}
//1: [XmlInclude(typeof(ChildA))]
//1: [XmlInclude(typeof(ChildB))]
public abstract class ChildClass {
public string ChildProp { get; set; }
}
public class ChildA : ChildClass {
public string AProp { get; set; }
}
public class ChildB : ChildClass {
public string BProp { get; set; }
}
static class Program {
static void Main() {
var ser = new XmlSerializer(typeof(MyWrapper));
var obj = new MyWrapper {
Data = new List<ChildClass> {
new ChildA { ChildProp = "abc", AProp = "def"},
new ChildB { ChildProp = "ghi", BProp = "jkl"}}
};
ser.Serialize(Console.Out, obj);
}
}

XmlSerializer doesn't serialize everything in my class

Following msdn:

Q: Why aren't all properties of collection classes serialized?

A: The XmlSerializer only serializes the elements in the collection when it detects either the IEnumerable or the ICollection interface. This behavior is by design. The only work around is to re-factor the custom collection into two classes, one of which exposes the properties including one of the pure collection types.

XML Serialize generic list of serializable objects

I have an solution for a generic List<> with dynamic binded items.

class PersonalList it's the root element

[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))] // include type class Person
public class PersonalList
{
[XmlArray("PersonenArray")]
[XmlArrayItem("PersonObjekt")]
public List<Person> Persons = new List<Person>();

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

// Konstruktoren
public PersonalList() { }

public PersonalList(string name)
{
this.Listname = name;
}

public void AddPerson(Person person)
{
Persons.Add(person);
}
}

class Person it's an single list element

[XmlType("Person")] // define Type
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]
// include type class SpecialPerson and class SuperPerson
public class Person
{
[XmlAttribute("PersID", DataType = "string")]
public string ID { get; set; }

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

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

[XmlElement("Age")]
public int Age { get; set; }

// Konstruktoren
public Person() { }

public Person(string name, string city, int age, string id)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
}
}

class SpecialPerson inherits Person

[XmlType("SpecialPerson")] // define Type
public class SpecialPerson : Person
{
[XmlElement("SpecialInterests")]
public string Interests { get; set; }

public SpecialPerson() { }

public SpecialPerson(string name, string city, int age, string id, string interests)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
this.Interests = interests;
}
}

class SuperPerson inherits Person

[XmlType("SuperPerson")] // define Type
public class SuperPerson : Person
{
[XmlArray("Skills")]
[XmlArrayItem("Skill")]
public List<String> Skills { get; set; }

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

public SuperPerson()
{
Skills = new List<String>();
}

public SuperPerson(string name, string city, int age, string id, string[] skills, string alias)
{
Skills = new List<String>();

this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
foreach (string item in skills)
{
this.Skills.Add(item);
}
this.Alias = alias;
}
}

and the main test Source

static void Main(string[] args)
{
PersonalList personen = new PersonalList();
personen.Listname = "Friends";

// normal person
Person normPerson = new Person();
normPerson.ID = "0";
normPerson.Name = "Max Man";
normPerson.City = "Capitol City";
normPerson.Age = 33;

// special person
SpecialPerson specPerson = new SpecialPerson();
specPerson.ID = "1";
specPerson.Name = "Albert Einstein";
specPerson.City = "Ulm";
specPerson.Age = 36;
specPerson.Interests = "Physics";

// super person
SuperPerson supPerson = new SuperPerson();
supPerson.ID = "2";
supPerson.Name = "Superman";
supPerson.Alias = "Clark Kent";
supPerson.City = "Metropolis";
supPerson.Age = int.MaxValue;
supPerson.Skills.Add("fly");
supPerson.Skills.Add("strong");

// Add Persons
personen.AddPerson(normPerson);
personen.AddPerson(specPerson);
personen.AddPerson(supPerson);

// Serialize
Type[] personTypes = { typeof(Person), typeof(SpecialPerson), typeof(SuperPerson) };
XmlSerializer serializer = new XmlSerializer(typeof(PersonalList), personTypes);
FileStream fs = new FileStream("Personenliste.xml", FileMode.Create);
serializer.Serialize(fs, personen);
fs.Close();
personen = null;

// Deserialize
fs = new FileStream("Personenliste.xml", FileMode.Open);
personen = (PersonalList)serializer.Deserialize(fs);
serializer.Serialize(Console.Out, personen);
Console.ReadLine();
}

Important is the definition and includes of the diffrent types.

How to add attributes for C# XML Serialization

Where do you have the type stored?

Normally you could have something like:

class Document {
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}

public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Document")]
public Document Document;
}

Serializing Lists of Classes to XML

Just to check, have you marked Bar as [Serializable]?

Also, you need a parameter-less ctor on Bar, to deserialize

Hmm, I used:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

Foo f = new Foo();

f.BarList = new List<Bar>();

f.BarList.Add(new Bar { Property1 = "abc", Property2 = "def" });

XmlSerializer ser = new XmlSerializer(typeof(Foo));

using (FileStream fs = new FileStream(@"c:\sertest.xml", FileMode.Create))
{
ser.Serialize(fs, f);
}
}
}

public class Foo
{
[XmlArray("BarList"), XmlArrayItem(typeof(Bar), ElementName = "Bar")]
public List<Bar> BarList { get; set; }
}

[XmlRoot("Foo")]
public class Bar
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}

And that produced:

<?xml version="1.0"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BarList>
<Bar>
<Property1>abc</Property1>
<Property2>def</Property2>
</Bar>
</BarList>
</Foo>


Related Topics



Leave a reply



Submit