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 withXmlAttributeOverrides
to avoid a severe memory leak. The documentation suggests using aHashtable
, howeverXmlAttributeOverrides
does not overrideEquals()
orGetHashCode()
, 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 wheneverXmlAttributeOverrides
is used.Given the complexity of finding and overriding the
XmlArrayItem
attributes of allList<A>
properties, you might consider sticking with the existingi:type
mechanism. It's simple, works well, is supported by bothDataContractSerializer
andXmlSerializer
, 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
Fake-Scrolling Containers with Very Many Controls
Why Does Visual Studio Type a Newly Minted Array as Nullable
How to Implement Gzip Compression in ASP.NET
In C#, How to Instantiate a Passed Generic Type Inside a Method
One Class Per File Rule in .Net
Compile and Run Dynamic Code, Without Generating Exe
The Object Cannot Be Deleted Because It Was Not Found in the Objectstatemanager
Memory Efficiency and Performance of String.Replace .Net Framework
How to Use Shell32 Within a C# Application
How Math.Pow (And So On) Actually Works
What Does System.Double[*] Mean
Handling the Window Closing Event with Wpf/Mvvm Light Toolkit
+= New Eventhandler(Method) VS += Method
Best Way to Split String into Lines
Performance of Find() VS. Firstordefault()
Set Cultureinfo in ASP.NET Core to Have a . as Currencydecimalseparator Instead of ,