Serializing Interface array - c#

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);
}

Related

Preserving restrictive access modifiers and making a class serializable

I'm trying to make a class I wrote serializable. It contains several static readonly fields that never change after construction, so they don't need to be serialized. I'm having trouble finding a way to re-set them after de-serialization that doesn't weaken the access in the base class. Here's a simplified example, starting with a non-serialisable base and derived class:
internal abstract class MyBase
{
private readonly List<int> myIntList = new List<int> ();
internal MyBase (List<int> anIntList)
{
this.myIntList = anIntList;
}
}
The derived class doesn't need to access the list, so the field can be private readonly in the base class, and the derived class set it like this:
internal sealed class MyDerived : MyBase
{
private static readonly List<int> derivedIntList = new List<int> { 1, 2, 3 };
internal MyDerived () : base (MyDerived.derivedIntList)
{
}
}
Now I want to make my derived class serializable. Since the list contents don't change, there's no need to serialize them, so I just put a DataContract attribute on both classes.
I serialize the derived class to disk like this:
private static void SeralizeDerived (string path)
{
MyDerived derived = new MyDerived ();
DataContractSerializer serializer = new DataContractSerializer (typeof (MyDerived));
using (FileStream stream = new FileStream (path, FileMode.Create))
{
serializer.WriteObject (stream, derived);
stream.Flush ();
}
}
and deserialize it like this:
private static void DeserializeDerived (string path)
{
DataContractSerializer serializer = new DataContractSerializer (typeof (MyDerived));
using (FileStream stream = new FileStream (path, FileMode.Open, FileAccess.Read))
{
MyDerived derived = serializer.ReadObject (stream) as MyDerived;
// debugger shows derived.myIntList as null, as expected
}
}
As per the comment in DeserializeDerived, the value of derived.myIntList is null. This isn't surprising to me - I didn't ask it to be serialized, and I haven't done anything to re-create it after deserialization.
The problem is this: The only way I know to fix this is to change the access to myIntList to make it protected, and have an OnDeserialized method in the MyDerived class that (re)sets myIntList:
private void ReInitialize ()
{
base.myIntList = MyDerived.derivedIntList;
}
[OnDeserialized]
private void OnDeserialized (StreamingContext context)
{
this.ReInitialize ();
}
internal MyDerived () : base ()
{
this.ReInitialize ();
}
This feels wrong - I don't want to weaken the access to the base class members, and initializing the members field-by-field in the derived constructor is more error-prone. The question is: How can I keep the same member protection but still support serialization?
Passing static list into the base class constructor looks strange for me. I would say that something is wrong with inheritance here.
If you still want to use inheritance try approach with abstract property:
public abstract class Data
{
public abstract int[] list { get;}
}
public class Data2 : Data
{
private static readonly int[] arr = new[] {1,2,3};
public override int[] list { get => arr; }
}

Deserialize class properties to derived class instead of base abstract class

The structure here is a bit convoluted so please bear with me. I hope that there's a way to do what a want to do, but if it's not possible then feel free to tell me!
Unfortunately I've not been involved in the design process of the XML file spec from the beginning, the goalposts have been moved a dozen times and the spec can't be amended for the sake of making it easier for me to do as any spec amendments are extortionately priced (yes, even renaming XML elements!)
Anyway I digress.
There are two different types of purchase order that have a slightly different file structure and slightly different processing logic and I am trying to handle them both with the same code instead of having two different projects for what is nearly identical.
Both types of purchase order derive from a set of abstract classes that determine basic business logic that both PO types share (quantity, cost, PO number, etc.).
Base classes
abstract class PO {
[XmlIgnore]
abstract POType PurchaseOrderType {get;}
[XmlIgnore]
abstract PO_Head Header {get;set;}
[XmlIgnore]
abstract List<PO_Line> LineItems {get;set;}
}
abstract class PO_Head {
[XmlIgnore]
abstract string PONumber {get;set;}
}
abstract class PO_Line {
[XmlIgnore]
abstract string ItemCode {get;set;}
[XmlIgnore]
abstract decimal UnitCost {get;set;}
[XmlIgnore]
abstract int OrderQty {get;set;}
}
Derived classes
class POR : PO {
// POR implementations
}
class POR_Head : PO_Line {
// POR implementations
}
class POR_Line : PO_Line {
// POR implementations
}
class CPO : PO {
// CPO implementations
}
class CPO_Head : PO_Line {
// CPO implementations
}
class CPO_Line : PO_Line {
// CPO implementations
}
The base abstract class is then used in the code to process each purchase order and import them into our accounting system.
for (int i = pending.Transactions.Count -1; i > -1; i--) {
PO pendingOrder = (PO)pending.Transactions[i];
// Import PO type
}
The problem is that when I attempt to deserialize into each derived class as appropriate, it attempts to deserialize the Header and LineItems into the derived types PO_Head and PO_Line. Is there any way I can explicitly tell the XmlSerializer to treat Header and LineItems as the derived class versions - CPO_Head and CPO_Line or POR_Head and POR_Line - depending on the class that is being serialized - CPO or POR respectively. Something akin to the below.
class CPO : PO {
[XmlIgnore]
override POType PurchaseOrderType => POType.CPO;
[XmlElement("CPO_Head")]
[XmlDeserializerType(typeof(CPO_Head))]
override PO_Head Header {get;set;}
[XmlArray("CPO_Lines")]
[XmlArrayItem("CPO_Line")]
[XmlDeserializerType(typeof(CPO_Line))]
override List<PO_Line> LineItems {get;set;}
}
Kind of dug myself a hole with this one (first time using deserialization) so I'm hoping there's an easy way out that would save me having to rewrite a ton of the work I've done!
TIA
EDIT - code used to deserialize/serialize as requested
public static void SerializeToXmlFile(object obj, FileInfo dstFile)
{
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
using (FileStream fs = dstFile.Open(FileMode.Create, FileAccess.Write))
{
xmlSerializer.Serialize(fs, obj);
}
}
public static object DeserializeFromXmlFile(FileInfo srcFile, Type type)
{
XmlSerializer xmlSerializer = new XmlSerializer(type);
using (FileStream fs = srcFile.Open(FileMode.Open, FileAccess.Read))
{
return xmlSerializer.Deserialize(fs);
}
}
public static void Main(string[] args)
{
// Deserialize from XML file
FileInfo srcFile = new FileInfo("path\to\file");
Type t;
if (srcFile.Name.Substring(0,3) == "CPO")
t = typeof(CPO);
else if (srcFile.Name.Substring(0,3) == "POR")
t = typeof(POR);
PO po = DeserializeFromXmlFile(file, t);
// Process the file
// ...
// Serialize back to file
FileInfo dstFile = new FileInfo("new\path\to\file");
SerializeToXmlFile(po, dstFile);
}
EDIT - fixed
As per the marked correct answer I was able to resolve this by specifying the Type in the XmlElement and XmlArrayItem attributes.
class POR {
[XmlElement(typeof(POR_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new POR_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(POR_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
class CPO {
[XmlElement(typeof(CPO_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new CPO_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(CPO_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
I think you are looking for the XmlArrayItemAttribute. According to the docs:
You can apply the XmlArrayItemAttribute to any public read/write member that returns an array, or provides access to one. For example, a field that returns an array of objects, a collection, an ArrayList, or any class that implements the IEnumerable interface.
The XmlArrayItemAttribute supports polymorphism--in other words, it allows the XmlSerializer to add derived objects to an array.
If I understand the example correctly, the attribute should be written with a list of the possible derived types that are allowed in the serialized enumerable, e.g.
[XmlArrayItem (typeof(PO_Line), ElementName = "PO_Line"),
XmlArrayItem (typeof(CPO_Line),ElementName = "CPO_Line")]
Since you are only passing a string, the attribute interprets that as the ElementName, not the type. The type must be passed using typeof(ClassName).

Serialization of class attributes?

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.

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

Serialize and DeSerialized derived classes from the base class

I am trying to create a base class where I can inherit from it (to add properties to the derived classes) and the utilized the Load and Save methods from the base class. I find myself writing the Load and Save over and over and I'd like to apply some DRY to it...
namespace Common
{
using System;
using System.IO;
using System.Xml.Serialization;
public abstract class ApplicationSettings
{
protected ApplicationSettings()
{
}
public static ApplicationSettings Load(string fileName)
{
if (!File.Exists(fileName))
{
return null;
}
XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettings));
using (StreamReader reader = new StreamReader(fileName))
{
ApplicationSettings param = (ApplicationSettings)serializer.Deserialize(reader);
reader.Close();
return param;
}
}
public void Save(string fileName)
{
XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettings));
using (StreamWriter writer = new StreamWriter(fileName))
{
serializer.Serialize(writer, this);
writer.Close();
}
}
}
}
Given this abstract class, I then want to derive a class such as:
namespace Common
{
using System;
public class ApplicationParameters : ApplicationSettings
{
public ApplicationParameters()
{
}
public string AuthorizationCode
{
get;
set;
}
public string ReferenceNumber
{
get;
set;
}
}
}
For the Derived class, I should be able to do something like
ApplicationParameters parameters =
ApplicationParmeters.Load("settings.xml");
However, in the implementation above, an compiler error occurs when I attempt to cast the ApplicationSettings to the ApplicationParameters class when I call the Load method in the base class.
Is there a way to do this?
Try replacing typeof(ApplicationSettings) with GetType().
Using this mechanism you will also tell the serializer that ApplicationParameters is a child class of ApplicationSettings. You do this via XmlInclude
[XmlInclude(typeof(ApplicationParameters))]
class ApplicationSettings
The latter is a requirements of the serializer because otherwise it won't know what class to instantiate.
Why are you using XmlSerializer ?
Unless you must control the way the output XML looks, DataContractSerializer is recommended
See here, for example
Make the top level class generic so that the Save/Load methods can support multiple types:
public abstract class ApplicationSettings<T>
{
public static T Load(string xml){ // Implementation }
public static void Save (T obj) { // Implementation }
}
public class ApplicationParameters : ApplicationSettings<ApplicationParameters>
{
}
Or you could just make the static methods themselves generic:
public abstract class ApplicationSettings
{
public static T Load<T>(string xml){ // implementation }
public static void Save<T>(T obj){ // implementation }
}
You will now notice that the Save/Load methods from the abstract parent class are strongly typed to the child so that the following line will work as expected:
ApplicationParameters parameters = ApplicationParameters.Load("settings.xml");
or
ApplicationParameters parameters =
ApplicationSettings.Load<ApplicationParameters>("settings.xml");
Depending on which method you use.
How about a constructor in your ApplicationParameters class that takes an ApplicationSettings as an argument and copy the shared properties from one to another? And then just set the not shared properties to be null or the default...

Categories

Resources