Serialization of class attributes? - c#

Is the default XmlSerializer capable of serializing class attributes as Xml attributes?
[MyClassTypeAttribute(ClassType.MyClass)]
public MyClass : BaseClass {
}
would turn to
<myclass MyClassType="MyClass">
Reason:
I have a WCF service that sends me different objects through the same operation contract which all derive from BaseClass. To know which type of object it is and to cast it directly (and serialize it as Xml to write in a document afterwards), I'd like to have some 'type' attribute (enum).
One possibility is, of course, declaring a property as XmlAttribute
[XmlAttribute(params)]
public MyClassType { get; set; }
Problem here is: The XmlSerializer (DataContractSerializer as well, AFAIK) forces me to have a setter on every property. I know I can declare the setter as protected and it still works (XmlSerializer, you naughty little thing), but don't really like that solution because 1) I think there is a reason that I'm able to leave out the setter in POCOs usually and 2) declaring some properties as XmlAttributes and others as XmlElements is confusing (it's like putting dogs and cats into a cat goulash.
(Additionally, is it possible to force a derived class to declare certain attributes?)
[abstract MyClassTypeAttribute]

if it is about the type of your class here is an example:
[XmlIncludeAttribute(typeof(ConcreteFooOne))]
[XmlIncludeAttribute(typeof(ConcreteFooTwo))]
[XmlIncludeAttribute(typeof(ConcreteFooThree))]
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public abstract partial class AbstractFoo
{
// Some abstract props etc.
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooOne : AbstractFoo
{
public int MyProp { get; set; }
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooTwo : AbstractFoo
{
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooThree : AbstractFoo
{
}
class Program
{
static void Main(string[] args)
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AbstractFoo));
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
serializer.Serialize(stream, new ConcreteFooOne() { MyProp = 10 });
stream.Flush();
}
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
var c = serializer.Deserialize(stream);
}
}
}
The code will serialize and include the type attribute and when you deserialize you will get the right instance.

Related

Serializing Interface array

I am trying to implement a way to save a set of objects to file, and then read it back to objects again.
I want to serialize the objects to XML (or JSON). The objects consists of one master object which holds an array of all the other objects. The array is of the type Interface, to allow several different types of child objects with some common functionality.
Obviously, there will be a problem during deserialization because the type of the interface object is not known.
Example:
[Serializable]
public class MasterClass
{
public ImyInterface[] subObjects;
}
public interface ImyInterface
{
}
How can I serialize/deserialize these objects?
My suggestions:
Add information about the object type in the serialized data.
Use a different solution than interface.
This is not the only way to serialize your data, but it is a ready to use solution from the framework:
DataContractSerializer supports this is you don't mind adding attributes for each of the available implementations of the interface:
[DataContract]
[KnownType(typeof(MyImpl))] // You'd have to do this for every implementation of ImyInterface
public class MasterClass
{
[DataMember]
public ImyInterface[] subObjects;
}
public interface ImyInterface
{
}
public class MyImpl : ImyInterface
{
...
}
Serializing/deserializing:
MasterClass mc = ...
using (var stream = new MemoryStream())
{
DataContractSerializer ser = new DataContractSerializer(typeof(MasterClass));
ser.WriteObject(stream, mc);
stream.Position = 0;
var deserialized = ser.ReadObject(stream);
}
For JSON you could use DataContractJsonSerializer instead.
One solution is to use an abstract class instead of an interface:
public class MasterClass
{
public MyAbstractClass[] subObjects;
}
[XmlInclude(typeof(MyImpl ))] //Include all classes that inherits from the abstract class
public abstract class MyAbstractClass
{
}
public class MyImpl : MyAbstractClass
{
...
}
It can be serialized/deserialized with the XmlSerializer:
MasterClass mc = ...
using (FileStream fs = File.Create("objects.xml"))
{
xs = new XmlSerializer(typeof(MasterClass));
xs.Serialize(fs, mc);
}
using (StreamReader file = new StreamReader("objects.xml"))
{
XmlSerializer reader = new XmlSerializer(typeof(MasterClass));
var deserialized = reader.Deserialize(file);
}

How to use XmlSerializer to serialize derived instances?

I realize this looks to be an exact duplicate of Using XmlSerializer to serialize derived classes, but I cannot figure out how to get this working following the guidance from that same question:
using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace xmlSerializerLab
{
public class Utf8StringWriter : System.IO.StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
[XmlRoot(ElementName = "Query", Namespace = "http://www.opengis.net/wfs")]
public class Query
{
[XmlElement(ElementName = "Filter", Namespace = "http://www.opengis.net/ogc")]
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlInclude(typeof(LiteralFilter))]
[XmlInclude(typeof(Query))]
[Serializable]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
public class PropertyIsOpFilter : Filter, IXmlSerializable
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
Program.ToXml(LeftOp, writer);
Program.ToXml(RightOp, writer);
}
}
[XmlRoot("IsEqualTo")]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
public class OpFilterBase : Filter, IXmlSerializable
{
public string Op { get; set; }
public object Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
if (!String.IsNullOrEmpty(Op))
{
writer.WriteStartElement(Op);
writer.WriteValue(Value);
writer.WriteEndElement();
}
else
{
writer.WriteValue(Value);
}
}
}
public class LiteralFilter : OpFilterBase { }
class Program
{
public static void ToXml(Object o, XmlWriter writer)
{
var inputSerializer = new XmlSerializer(o.GetType(), new Type[] {
typeof(Filter),
typeof(PropertyIsOpFilter),
typeof(PropertyIsEqualToFilter),
typeof(OpFilterBase),
typeof(LiteralFilter),
typeof(Query)
});
inputSerializer.Serialize(writer, o);
}
public static string ToXml(Object o)
{
var inputSerializer = new XmlSerializer(o.GetType());
using (var writer = new Utf8StringWriter())
{
using (var xmlWriter = new XmlTextWriter(writer))
{
ToXml(o, xmlWriter);
}
return writer.ToString();
}
}
static void Main(string[] args)
{
Filter o = new PropertyIsEqualToFilter()
{
LeftOp = new LiteralFilter()
{
Value = 1
},
RightOp = new LiteralFilter()
{
Value = 1
}
};
var query = new Query()
{
Filter = o
};
Console.WriteLine(ToXml(query));
Console.ReadLine();
}
}
}
It results in this exception:
InvalidOperationException: The type
xmlSerializerLab.PropertyIsEqualToFilter may not be used in this
context. To use xmlSerializerLab.PropertyIsEqualToFilter as a
parameter, return type, or member of a class or struct, the parameter,
return type, or member must be declared as type
xmlSerializerLab.PropertyIsEqualToFilter (it cannot be object).
Objects of type xmlSerializerLab.PropertyIsEqualToFilter may not be
used in un-typed collections, such as ArrayLists.
As far as I can tell, I need the IXmlSerializable on the PropertyIsOpFilter and OpFilterBase because I'm trying to target a specific XML format described by this schema. But I'm finding that I also have to make the Query class IXmlSerializable.
Here is a sample XML document that I'd like to be able to produce from the model:
<GetFeature xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
service="WFS"
version="1.1.0"
maxFeatures="0" xmlns="http://www.opengis.net/wfs">
<ResultType>Results</ResultType>
<OutputFormat>text/gml; subtype=gml/3.1.1</OutputFormat>
<Query
d2p1:srsName="EPSG:4326" xmlns:d2p1="http://www.opengis.net/ogc">
<d2p1:Filter>
<d2p1:IsEqualTo>
<d2p1:PropertyName>Prop1</d2p1:PropertyName>
<d2p1:Literal>1</d2p1:Literal>
</d2p1:IsEqualTo>
</d2p1:Filter>
</Query>
</GetFeature>
By making the Query class IXmlSerializable and writing a good bit of WriteXml and ReadXml logic I can get it to work, but I'd expect it to work without having to do all that since the XmlRoot and XmlAttribute and XmlElement tags should give enough information to the serializer for it to know which class to instantiate based on the tag name (match ElementName) and certainly how to serialize based on the attributes.
The problem you are seeing can be reproduced with the following minimal example:
public class BaseClass
{
}
public class DerivedClass : BaseClass, IXmlSerializable
{
#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) { throw new NotImplementedException(); }
public void WriteXml(XmlWriter writer) { }
#endregion
}
Using the serialization code:
BaseClass baseClass = new DerivedClass();
using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter))
{
var serializer = new XmlSerializer(typeof(BaseClass), new Type[] { typeof(DerivedClass) });
serializer.Serialize(xmlWriter, baseClass);
}
Console.WriteLine(textWriter.ToString());
}
The following exception is thrown (sample fiddle #1):
System.InvalidOperationException: There was an error generating the XML document.
---> System.InvalidOperationException: The type DerivedClass may not be used in this context. To use DerivedClass as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type DerivedClass (it cannot be object). Objects of type DerivedClass may not be used in un-typed collections, such as ArrayLists.
This is among the most unhelpful exception messages I have seen from XmlSerializer. To understand the exception, you need to understand how XmlSerializer handles polymorphism via the [XmlInclude] mechanism. If I remove IXmlSerializable from DerivedClass, the following XML is generated (fiddle #2):
<BaseClass xsi:type="DerivedClass" />
Notice the xsi:type type attribute? That is a w3c standard attribute that XmlSerializer uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer is deserializing a polymorphic type to which [XmlInclude] attributes have been applied (either statically or through the constructor you are using), it will look for the xsi:type attribute to determine the actual type to construct and deserialize.
It is this, apparently, which conflicts with IXmlSerializable. A type which implements this interface should completely control its XML reading and writing. However, by parsing and interpreting the xsi:type attribute, XmlSerializer has already begun automatic deserialization, and so throws an exception due to the inconsistent deserialization strategies of the base and derived types.
What's more, adding IXmlSerializable to the base type doesn't really fix the problem either If you do so, the xsi:type attribute is never written, and later, when ReadXml() is called, an object of the base type will get unconditionally constructed, as shown in fiddle #3.
(It's conceivable that Microsoft could have implemented a special case where XmlSerializer begins automatic deserialization, then "backs off" and hands the task over to ReadXml() when an IXmlSerializable polymorphic type is encountered and constructed. But, they did not.)
The solution would seem to be to serialize your Filter types automatically using the [XmlInclude] mechanism. In fact I don't see any reason you need to use IXmlSerializable, and was able to serialize your model successfully by removing IXmlSerializable completely and making some minor changes to namespaces:
public static class XmlNamespaces
{
public const string OpengisWfs = "http://www.opengis.net/wfs";
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Query
{
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsOpFilter : Filter
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
}
[XmlRoot("IsEqualTo", Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
[XmlInclude(typeof(LiteralFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class OpFilterBase : Filter
{
public string Op { get; set; }
public object Value { get; set; }
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class LiteralFilter : OpFilterBase { }
Notes:
For the [XmlInclude] mechanism to work, all the included types apparently must be in the same XML namespace as the base type. To ensure this I added [XmlRoot(Namespace = XmlNamespaces.OpengisWfs)] to all the Filter subtypes.
The [XmlInclude(typeof(DerivedType))] attributes can be added either to their immediate parent type or to the lowest common base type. In the code above I added the attributes to the immediate parent types so that members of an intermediate type could be serialized successfully, e.g.:
public class SomeClass
{
PropertyIsOpFilter IsOpFilter { get; set; }
}
Consider marking intermediate types that cannot be instantiated as abstract, e.g. public abstract class Filter. Consider marking types that are "most derived" as sealed, e.g. public sealed class LiteralFilter
If you use the new XmlSerializer(Type, Type []) constructor, you must statically cache the serializer to avoid a severe memory leak, as explained here. It's not necessary in my solution but you are using it in your question.
Sample fiddle #4 showing that the following XML is generated successfully:
<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs">
<Filter xsi:type="PropertyIsEqualToFilter">
<LeftOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</LeftOp>
<RightOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</RightOp>
</Filter>
</Query>

C# Xml-Serialization of abstract base type into derived type - which TypeName property definition wins?

My scenario:
I have an object that I have defined with properties that are decorated with XmlElement tags and that have types that I have defined, some of which are typed as abstract that get set to respective derived types. I want to serialize this entire object into XML using XmlSerializer, and all properties that are abstract should get serialized as elements with TypeName set to the TypeName of the derived type.
This is an example of how the objects are structured:
[XmlType(TypeName = "MAINOBJECT")]
public class MainObject
{
[XmlElement(Type = typeof(DerivedClass))]
public BaseClass TheBase { get; set; }
}
[XmlInclude(typeof(DerivedClass))]
public abstract class BaseClass
{
[XmlAttribute("AnAttribute")]
public string AnAttribute { get; set; }
[XmlElement("ANELEMENT")]
public string AnElement { get; set; }
}
[XmlType(TypeName = "DERIVEDCLASS")]
public class DerivedClass : BaseClass
{
[XmlElement("ANOTHERELEMENT")]
public string AnotherElement { get; set; }
}
Note, however, that when I create a new instance of MainObject, populate it's properties and serialize it, this is what the generated XML looks like:
<MAINOBJECT>
<BaseClass AnAttribute="">
<ANELEMENT/>
<ANOTHERELEMENT/>
</BaseClass>
</MAINOBJECT>
What I want is this:
<MAINOBJECT>
<DERIVEDCLASS AnAttribute="">
<ANELEMENT/>
<ANOTHERELEMENT/>
</DERIVEDCLASS>
</MAINOBJECT>
Any clue what I'm doing wrong here?
Add the XmlElement name to TheBase in MainObject as follows:
[XmlType(TypeName = "MAINOBJECT")]
public class MainObject
{
[XmlElement("DERIVEDCLASS", Type = typeof(DerivedClass))]
public BaseClass TheBase { get; set; }
}
It seems to me that the best solution here would be to implement the IXmlSerializable interface so that you can have complete control over how the objects get serialized. Sure, it's more work, but if you have requirements like this that are somewhat out of the ordinary, then you may also run into more quirks where the standard XmlSerializer won't work for you down the road.
There is a good tutorial here: How to Implement IXmlSerializable Correctly
Also, there is some good information here: Proper way to implement IXmlSerializable?
Maybe not the best solution, but:
class Program
{
static void Main(string[] args)
{
XmlAttributes attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "DerivedClass";
attr.Type = typeof(DerivedClass);
attrs.XmlElements.Add(attr);
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
attrOverrides.Add(typeof(MainObject), "TheBase", attrs);
XmlSerializer s = new XmlSerializer(typeof(MainObject), attrOverrides);
StringWriter writer = new StringWriter();
MainObject mo = new MainObject { TheBase = new DerivedClass { AnAttribute = "AnAttribute", AnElement = "AnElement", AnotherElement = "AotherElement" } };
s.Serialize(writer, mo);
Console.Write(writer.ToString());
}
}

How to deserialize concrete implementation of abstract class from XML

I have an abstract class with a couple of concrete implementations. This needs serializing to XML in order to send to another system - this is working fine. However, I also need to be able to deserialize the same XML structure back. No matter what I try, I don't seem to be able to do this. My class structure is as below:
Abstract Class:
[XmlIncludeAttribute(typeof(ConcreteFooOne))]
[XmlIncludeAttribute(typeof(ConcreteFooTwo))]
[XmlIncludeAttribute(typeof(ConcreteFooThree))]
[XmlRoot(ElementName = "FooData", Namespace="http://foo.bar")]
public abstract partial class AbstractFoo
{
// Some abstract props etc.
}
Concrete Class Example:
public partial class ConcreteFooOne : AbstractFoo
{
// Some properties, constructor etc.
}
XML Root Example:
<FooData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ConcreteFooOne" RequestResponse="Request" xmlns="http://foo.bar">
Only included XML root as example as this appears to be where the issue is. Now I can serialize fine, but on the deserialization, if I deserialize by passing in the abstract type, I of course get an exception stating that type "AbstractFoo" is abstract. So I simply changed the logic so that instead the concrete type (ConcreteFooOne in this case) is passed to the serializer. Now I get a "http://foo.bar'> was not expected". I am presuming that this is because the serializer doesn't know what to root node should be?
I have the root node defined on the abstract class as this will be the same for all concrete implementations. The concrete type is defined by the "RequestResponse" attribute (or the xsi:type attribute can work too if it is present as that gives us the actual type name). Is there a way to make the serializer pick up what is required off the abstract class or am I going completely the wrong way about this?
Note that I can't change the class structure too much as it is based very closely off some XML schemas provided by a 3rd party.
Thanks in advance for anyone's help with this, it would be much appreciated.
Add [XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")] to the sub-classes
here is an example I made:
[XmlIncludeAttribute(typeof(ConcreteFooOne))]
[XmlIncludeAttribute(typeof(ConcreteFooTwo))]
[XmlIncludeAttribute(typeof(ConcreteFooThree))]
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public abstract partial class AbstractFoo
{
// Some abstract props etc.
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooOne : AbstractFoo
{
public int MyProp { get; set; }
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooTwo : AbstractFoo
{
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooThree : AbstractFoo
{
}
class Program
{
static void Main(string[] args)
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AbstractFoo));
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
serializer.Serialize(stream, new ConcreteFooOne() { MyProp = 10 });
stream.Flush();
}
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
var c = serializer.Deserialize(stream);
}
}
}
it's simple, in the client when you deserealize, define a XmlSerializer like:
XmlSerializer xs= new XmlSerializer (typeof (AbstractFoo),
new Type[] { typeof (ConcreteFooOne), typeof (ConcreteFooTwo) } );
then you can try:
//it instantiate the correct class, need a streamreader
var myclass = xs.Deserialize(reader);
if (myclass is ConcreteFooOne)
//do something
if (myclass is ConcreteFooTwo)
//do something

How to force XML deserialization to derived types of the same name?

I have types provided in a library I cant modify, such as this:
namespace BaseNamespace
{
public class A
{
public string Foo { get; set; }
}
}
I also have a class named SomeOtherNamespace.A" that derives from BaseNamespace.A like this:
namespace SomeOtherNamespace
{
public class A : BaseNamespace.A
{
public string DoSomething() {}
}
}
From a web service I receive an XML payload with in it.
I want to deserialize the XML so that I end up with a SomeOtherNamespace.A object. However when I run the following code
string xml = "<A Foo=\"test\"></A>";
XmlSerializer serializer = new XmlSerializer(typeof(A));
StringReader reader = new StringReader(xml);
A obj = serializer.Deserialize(reader) as A;
I get an error:
Types 'BaseNamespace.A' and 'SomeOtherNamespace.A' both use the XML
type name, 'A', from namespace ''. Use XML attributes to specify a
unique XML name and/or namespace for the type.
Question: Without modification of the class BaseNamespace.A how can I force deserialization to my derived type SomeOtherNamespace.A?
Rename your SomeOtherNamespace.A as
namespace SomeOtherNamespace
{
public class AAA : BaseNamespace.A
{
public string DoSomething() {}
}
}
and create serializer as
XmlRootAttribute root = new XmlRootAttribute("A");
XmlSerializer serializer = new XmlSerializer(typeof(AAA),root);
If it is acceptable to use a different name in Xml for SomeOtherNamespace.A, you can just use XmlTypeAttribute to give A a different Xml type.
namespace SomeOtherNamespace {
[XmlType("NotA")]
public class A
{
public string Foo { get; set; }
}
}
More importantly, you may want to consider a design in which your derived class does not have the same name as the base class.

Categories

Resources