I've got a simple .Net framework C# console app that serializes a a class that is of a derived type, where a property is also of a derived type.
The derived classes have names that are the same as the base class, but are in a different namespace to prevent them from clashing. It seems though that the reflection the XmlSerializer
uses does not work with this too well. Maybe there is some way of wrangling the attributes that I can still end up with the base class using pretty names (as it will be a DLL interface when used) and the XML also using pretty names (as it will be human editable)... pretty names for the derived classes are not required (though would be a bonus).
The XML would hopefully look like:
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Details>
<Detail>
<Description>bald</Description>
</Detail>
<Detail>
<Description>red tie</Description>
</Detail>
</Details>
</Person>
But the closest I can get without exceptions is where the <Detail>
elements are
<Detail xsi:type="DerivedDetail"> ... </Detail>
Having to add this xs:type
attribute is not the best for human-editable XML.
This is achieved with the below C# code. If I remove the marked XmlType
attribute then the element should serialize without the xsi:type
attribute, but instead I get an exception:
InvalidOperationException: Types 'Test.Detail' and 'Test.Xml.Detail' both use the XML type name, 'Detail', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
I tried marking the derived Xml.Detail
class as an anonymous XML type but then the exception reads:
InvalidOperationException: Cannot include anonymous type 'Test.Xml.Detail'.
I have read many similar questions but have not encountered anything that solves this just yet.
In this code below Person
is an abstract class that has a property that is an array of the abstract type Detail
. These types are derived by Xml.Person
and Xml.Detail
respectively. The program creates a test Xml.Person
object and attempts to serialize it:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// Create test details array
var TestDetails = new Xml.Detail[]
{
new Xml.Detail
{
Description = "bald"
},
new Xml.Detail
{
Description = "red tie"
}
};
// create test person object that holds details array
var TestBar = new Xml.Person()
{
Details = TestDetails
};
// serialize the person object
var s = new Xml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
// base classes
public abstract class Person
{
public abstract Detail[] Details { get; set; }
}
public abstract class Detail
{
public abstract string Description { get; set; }
}
namespace Xml
{
// derived classes
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(IsNullable = false)]
public class Person : Test.Person
{
[XmlArrayItem("Detail", typeof(Detail))]
[XmlArray(IsNullable = false)]
public override Test.Detail[] Details { get; set; }
}
// This attribute makes serialization work but also adds the xsi:type attribute
[XmlType("DerivedDetail")]
[Serializable]
public class Detail : Test.Detail
{
public override string Description { get; set; }
}
// class that does serializing work
public class Serializer
{
private static XmlSerializer PersonSerializer =
new XmlSerializer(typeof(Person), new Type[] { typeof(Detail) });
public string Serialize(Test.Person person)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
PersonSerializer.Serialize(Writer, person);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
}
}
Not sure why you're using base classes instead of interfaces when you don't have any member fields. Regardless, I assumed you wanted Xml.Person
to be a concrete instantiation of abstract Person
or any classes derived from abstract Person
without decorating abstract Person
with XML attributes. I accomplished this by forcing abstract Person
to become a concrete instantiation of Xml.Person
before serializing it. Please replace XmlSerializationProject
with Test
.
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace XmlSerializationProject
{
class Program
{
static void Main(string[] args)
{
// Create test details array
var TestDetails = new Xml.Detail[]
{
new Xml.Detail
{
Description = "bald"
},
new Xml.Detail
{
Description = "red tie"
}
};
// create test person object that holds details array
var TestBar = new Xml.Person()
{
Details = TestDetails
};
// serialize the person object
var s = new Xml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
Console.ReadKey();
}
}
// base classes
public abstract class Person
{
public abstract Detail[] Details { get; set; }
}
public abstract class Detail
{
public abstract string Description { get; set; }
}
namespace Xml
{
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(IsNullable = false)]
public class Person : XmlSerializationProject.Person
{
public Person()
{ }
public Person(XmlSerializationProject.Person person)
{
// Deep copy
if (person.Details == null) return;
this.Details = new Detail[person.Details.Length];
for (int i = 0; i < person.Details.Length; i++)
{
this.Details[i] = new Detail { Description = person.Details[i].Description };
}
}
[XmlArray(ElementName = "Details")]
[XmlArrayItem("Detail", typeof(Detail))]
[XmlArrayItem("ODetail", typeof(XmlSerializationProject.Detail))]
public override XmlSerializationProject.Detail[] Details
{
get;
set;
}
}
[Serializable]
public class Detail : XmlSerializationProject.Detail
{
public override string Description { get; set; }
}
// class that does serializing work
public class Serializer
{
private static readonly XmlSerializer PersonSerializer;
private static Serializer()
{
var xmlAttributeOverrides = new XmlAttributeOverrides();
// Change original "Detail" class's element name to "AbstractDetail"
var xmlAttributesOriginalDetail = new XmlAttributes();
xmlAttributesOriginalDetail.XmlType = new XmlTypeAttribute() { TypeName = "AbstractDetail" };
xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Detail), xmlAttributesOriginalDetail);
// Ignore Person.Details array
var xmlAttributesOriginalDetailsArray = new XmlAttributes();
xmlAttributesOriginalDetailsArray.XmlIgnore = true;
xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Person), "Details", xmlAttributesOriginalDetailsArray);
PersonSerializer = new XmlSerializer(
typeof(Person), xmlAttributeOverrides, new Type[] { typeof(Detail) }, new XmlRootAttribute(), "default");
}
public string Serialize(XmlSerializationProject.Person person)
{
return Serialize(new Person(person));
}
public string Serialize(Person person)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
PersonSerializer.Serialize(Writer, person);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
}
}