I have a class which needs a list of type-value pairs of arbitrary length.
For example one instance may hold { (string, "hello"), (int, 3) }, and another may hold { (char, 'a') }. The type will only ever be a primative type, and I could limit to only int and string, but I would rather it be possible to include float, char, etc to keep to flexible.
I need to serialize and deserialize instances of this class. My preferred serialization is XML using System.Xml.Serialization.XmlSerializer.
Tuple<Type, object>
was no good because neither Tuple nor object serializes by default, so I defined a custom struct:
[Serializable]
public struct ObjectDataItem { public Type Type; public string Value; }
and my class holds a list of ObjectDataItems (string was fine for Value as I guess some type conversion is inevitable somewhere anyway).
My question is how I do I deserialize the ObjectDataItem (in particular deserializing the 'Type' variable)?
I am currently deserializing using the code:
public static M LoadXML<M>(string fileName) where M : struct
{
if (File.Exists(fileName))
{
FileStream loadStream = new FileStream(fileName, FileMode.Open);
XmlSerializer serializer = new XmlSerializer(typeof(M));
M fileLoaded = (M)serializer.Deserialize(loadStream);
loadStream.Close();
return fileLoaded;
}
else throw new HException("File not found: {0}", fileName);
}
You can save only the type name and restore it after serializing or in the property itself
new ObjectDataItem { TypeName = typeof(int).FullName, Value = 3.ToString() }
...
public struct ObjectDataItem
{
public string TypeName;
public string Value;
[XmlIgnore]
public Type RealType
{
get
{
return Type.GetType(TypeName);
}
}
}
If you will make your Value field of type object Serializer would stores the type in XML and put the correct type in the deserialization
<Value xsi:type="xsd:int">3</Value>
and your DataObjectItem will looks like
public struct ObjectDataItem
{
public object Value;
[XmlIgnore]
public Type Type
{
get
{
return Value.GetType();
}
}
}
Related
I'm trying to design an application that will allow the user to specify an Enum type in an XML, and from that the application will execute a specific method tied to that enum (using a dictionary). I'm getting hung up on the Enum portion of the XML.
public class TESTCLASS
{
private Enum _MethodType;
[XmlElement(Order = 1, ElementName = "MethodType")]
public Enum MethodType
{
get { return _MethodType; }
set { _MethodType = value; }
}
public TESTCLASS() { }
public TESTCLASS(Enummies.BigMethods bigM)
{
MethodType = bigM;
}
public TESTCLASS(Enummies.SmallMethods smallM)
{
MethodType = smallM;
}
}
public class Enummies
{
public enum BigMethods { BIG_ONE, BIG_TWO, BIG_THREE }
public enum SmallMethods { SMALL_ONE, SMALL_TWO, SMALL_THREE }
}
And then trying to serialize the TESTCLASS results in an exception:
string p = "C:\\testclass.xml";
TESTCLASS testclass = new TESTCLASS(Enummies.BigMethods.BIG_ONE);
TestSerializer<TESTCLASS>.Serialize(p, testclass);
System.InvalidOperationException: The type Enummies+BigMethods may not be used in this context.
My serialization method looks like this:
public class TestSerializer<T> where T: class
{
public static void Serialize(string path, T type)
{
var serializer = new XmlSerializer(type.GetType());
using (var writer = new FileStream(path, FileMode.Create))
{
serializer.Serialize(writer, type);
}
}
public static T Deserialize(string path)
{
T type;
var serializer = new XmlSerializer(typeof(T));
using (var reader = XmlReader.Create(path))
{
type = serializer.Deserialize(reader) as T;
}
return type;
}
}
I tried including some checking/casting in the MethodType Getter, but this results in the same error.
public Enum MethodType
{
get
{
if (_MethodType is Enummies.BigMethods) return (Enummies.BigMethods)_MethodType;
if (_MethodType is Enummies.SmallMethods) return (Enummies.SmallMethods)_MethodType;
throw new Exception("UNKNOWN ENUMMIES TYPE");
}
set { _MethodType = value; }
}
When I try to serialize your class with XmlSerializer, the innermost exception I get is:
Message="System.Enum is an unsupported type. Please use [XmlIgnore] attribute to exclude members of this type from serialization graph."
This is self-explanatory: you cannot serialize a member whose type is the abstract type System.Enum.
You can, however, serialize a member of type System.Object provided that all possible types of value that might be encountered are declared statically by using [XmlInclude(typeof(T))]. Thus you can modify your type as follows:
// Include all possible types of Enum that might be serialized
[XmlInclude(typeof(Enummies.BigMethods))]
[XmlInclude(typeof(Enummies.SmallMethods))]
public class TESTCLASS
{
private Enum _MethodType;
// Surrogate object property for MethodObject required by XmlSerializer
[XmlElement(Order = 1, ElementName = "MethodType")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public object MethodTypeObject
{
get { return MethodType; }
set { MethodType = (Enum)value; }
}
// Ignore the Enum member that cannot be serialized directly
[XmlIgnore]
public Enum MethodType
{
get { return _MethodType; }
set { _MethodType = value; }
}
public TESTCLASS() { }
public TESTCLASS(Enummies.BigMethods bigM)
{
MethodType = bigM;
}
public TESTCLASS(Enummies.SmallMethods smallM)
{
MethodType = smallM;
}
}
And XML will be generated as follows:
<TESTCLASS xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MethodType xsi:type="BigMethods">BIG_THREE</MethodType>
</TESTCLASS>
Or
<?xml version="1.0" encoding="utf-16"?>
<TESTCLASS xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MethodType xsi:type="SmallMethods">SMALL_TWO</MethodType>
</TESTCLASS>
Notice the xsi:type attribute? That is a W3C standard attribute that an element may use to explicitly assert its type. Microsoft uses this attribute to represent type information for polymorphic elements as explained here.
Sample fiddle.
You might want to check that the value type is a known Enum type in the setter for MethodObject (rather than the getter) but this is not required for XML serialization.
I use the following class to exchange JSON data over two ASP.NET services :
[DataContract]
public class Filter
{
[DataMember]
public string Name {get; set;}
[DataMember]
public FilterOperator Operator {get; set;}
[DataMember]
public object Value {get; set;}
}
Here is the problem : if I set a DateTime inside Value, it will be deserialized as string :
Value = "/Date(1476174483233+0200)/"
This is probably because deserializer has no clue to know what was the type of the value when serialized initially :
JSON = {"Value":"\/Date(1476174483233+0200)\/"}
As explained here, DataContractJsonSerializer supports polymorphism, with the help of the __type property.
I have tried to add [KnownType(typeof(DateTime))] attribute on the top of the class but it does not help.
However if I set a Tuple<DateTime> inside Value property (and the appropriate KnownType attribute on the class), it works (the value it deserialized properly) :
Value = {(10/11/2016 10:49:30 AM)}
Inside JSON, __type is emited
JSON = {
"Value": {
"__type" : "TupleOfdateTime:#System",
"m_Item1" : "\/Date(1476175770028+0200)\/"
}
}
Is there a way to force DataContractJsonSerializer to emit proper information to serialize/deserialize DateTime properly (which mean I got a DateTime after serialization instead of a string) ?
I have try to set EmitTypeInformation = EmitTypeInformation.Always in DataContractJsonSerializerSettings but it does not help.
The problem is that DataContractJsonSerializer only inserts a polymorphic type hint property "__type" for types that correspond to a JSON object - an unordered set of name/value pairs surrounded by { and }. If the type maps to anything else (i.e. a JSON array or primitive) then there is no place for a type hint to be inserted. This restriction is documented in Stand-Alone JSON Serialization:
Type Hints Apply Only to Complex Types
There is no way to emit a type hint for non-complex types. For example, if an operation has an Object return type but returns a Circle, the JSON representation can be as shown earlier and the type information is preserved. However, if Uri is returned, the JSON representation is a string and the fact that the string used to represent a Uri is lost. This applies not only to primitive types but also to collections and arrays.
Thus what you will need to do is to modify your Filter class to serialized and deserialized a generic surrogate object for its value that encapsulates the value's type information, along the lines the one in this question for Json.Net:
[DataContract]
public class Filter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public FilterOperator Operator { get; set; }
[IgnoreDataMember]
public object Value { get; set; }
[DataMember]
TypedSurrogate TypedValue
{
get
{
return TypedSurrogate.CreateSurrogate(Value);
}
set
{
if (value is TypedSurrogate)
Value = ((TypedSurrogate)value).ObjectValue;
else
Value = value;
}
}
}
[DataContract]
// Include some well-known primitive types. Other types can be included at higher levels
[KnownType(typeof(TypedSurrogate<string>))]
[KnownType(typeof(TypedSurrogate<bool>))]
[KnownType(typeof(TypedSurrogate<byte>))]
[KnownType(typeof(TypedSurrogate<sbyte>))]
[KnownType(typeof(TypedSurrogate<char>))]
[KnownType(typeof(TypedSurrogate<short>))]
[KnownType(typeof(TypedSurrogate<ushort>))]
[KnownType(typeof(TypedSurrogate<int>))]
[KnownType(typeof(TypedSurrogate<long>))]
[KnownType(typeof(TypedSurrogate<uint>))]
[KnownType(typeof(TypedSurrogate<ulong>))]
[KnownType(typeof(TypedSurrogate<float>))]
[KnownType(typeof(TypedSurrogate<double>))]
[KnownType(typeof(TypedSurrogate<decimal>))]
[KnownType(typeof(TypedSurrogate<DateTime>))]
[KnownType(typeof(TypedSurrogate<Uri>))]
[KnownType(typeof(TypedSurrogate<Guid>))]
[KnownType(typeof(TypedSurrogate<string[]>))]
public abstract class TypedSurrogate
{
protected TypedSurrogate() { }
[IgnoreDataMember]
public abstract object ObjectValue { get; }
public static TypedSurrogate CreateSurrogate<T>(T value)
{
if (value == null)
return null;
var type = value.GetType();
if (type == typeof(T))
return new TypedSurrogate<T>(value);
// Return actual type of subclass
return (TypedSurrogate)Activator.CreateInstance(typeof(TypedSurrogate<>).MakeGenericType(type), value);
}
}
[DataContract]
public class TypedSurrogate<T> : TypedSurrogate
{
public TypedSurrogate() : base() { }
public TypedSurrogate(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
[DataMember]
public T Value { get; set; }
}
Now your JSON will look something like:
{
"TypedValue": {
"__type": "TypedSurrogateOfdateTime:#Question39973917",
"Value": "/Date(1476244800000)/"
}
}
Is is possible to serialize a custom struct as an xml attribute?
Sample code:
public class Dummy<T>
{
private Dummy() { }
public Dummy(T item1)
{
Item1 = item1;
Item2 = item1;
}
public T Item1 { get; set; }
[XmlAttribute]
public T Item2 { get; set; }
}
public struct Meh
{
public int Prop { get; set; }
}
[Test]
public void XmlMehTest()
{
var meh = new Meh{Prop = 1};
var dummy = new Dummy<Meh>(meh);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
// System.InvalidOperationException : Cannot serialize member 'Meh2' of type Meh.
// XmlAttribute/XmlText cannot be used to encode complex types.
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
[Test]
public void XmlDateTimeTest()
{
var dummy = new Dummy<DateTime>(DateTime.Now);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
Please ignore that the struct is mutable, wrote it like that for a compact sample.
This is truly a first-world-developer-problem but I'm still curious :)
The documentation says:
You can assign the XmlAttributeAttribute only to public fields or public properties that return a value (or array of values) that can be mapped to one of the XML Schema definition language (XSD) simple types (including all built-in datatypes derived from the XSD anySimpleType type). The possible types include any that can be mapped to the XSD simple types, including Guid, Char, and enumerations.
So to do this, we should be able to create our own type definition for XSD,I guess we can do that.Because this documentation contains full explanation about it.But what we can't do is, we can't include our definition to this list.Initially XML Serializer uses these types to figure out your type's XSD type definition.You can use this attribute with DateTime because it's definition creating with this method and storing in a HashTable:
AddPrimitive(typeof(DateTime), "dateTime", "DateTime",
TypeFlags.XmlEncodingNotRequired |
TypeFlags.HasCustomFormatter |
TypeFlags.CanBeElementValue | **TypeFlags.CanBeAttributeValue**);
AddPrimitive method:
private static void AddPrimitive(Type type, string dataTypeName, string formatterName, TypeFlags flags)
{
XmlSchemaSimpleType dataType = new XmlSchemaSimpleType {
Name = dataTypeName
};
TypeDesc desc = new TypeDesc(type, true, dataType, formatterName, flags);
if (primitiveTypes[type] == null)
{
primitiveTypes.Add(type, desc);
}
primitiveDataTypes.Add(dataType, desc);
primitiveNames.Add(dataTypeName, "http://www.w3.org/2001/XMLSchema", desc);
}
And this definition calling from XmlReflectionImporter like this (which is generating the exception according to StackTrace):
this.GetTypeDesc(name, ns, TypeFlags.CanBeElementValue | TypeFlags.CanBeTextValue | TypeFlags.CanBeAttributeValue);
I guess most important thing is here TypeFlags.CanBeAttributeValue and I think it's specify that this type can be attibute value.So as a result maybe we can serialize custom structs as an XmlAttirube but we can't do it with standart XmlSerializer.Because as I said it's using this list to figure out XSD type definition.And it's an initial list and it's impossible to add new element to that list.
P.S. You might want take a look at here http://msdn.microsoft.com/en-us/library/8w07bk3h(v=vs.80).aspx
I've got a class which has been serialized into JSON, and which I'm trying to deserialize into an object.
e.g.
public class ContentItemViewModel
{
public string CssClass { get; set; }
public MyCustomClass PropertyB { get; set; }
}
the simple property (CssClass) will deserialize with:
var contentItemViewModels = ser.Deserialize<ContentItemViewModel>(contentItems);
But PropertyB gets an error...
We added a JavaScriptConverter:
ser.RegisterConverters(new List<JavaScriptConverter>{ publishedStatusResolver});
But when we added 'MyCustomClass' as a 'SupportedType', the Deserialize method was never called. However when we have ContentItemViewModel as the SupportedType, then Deserialize is called.
We've got a current solution which looks something like this:
class ContentItemViewModelConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var cssClass = GetString(dictionary, "cssClass"); //I'm ommitting the GetString method in this example...
var propertyB= GetString(dictionary, "propertyB");
return new ContentItemViewModel{ CssClass = cssClass ,
PropertyB = new MyCustomClass(propertyB)}
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new Exception("Only does the Deserialize");
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new List<Type>
{
typeof(ContentItemViewModel)
};
}
}
}
But we'd prefer a simpler solution of only deserializing MyCustomClass, as there are a number of other fields which are on the ViewModel, and it seems a waste to have to edit this converter every time we change/add a property....
Is there a way to Deserialize JUST PropertyB of type MyCustomClass?
Thanks for your help!
Have you considered using DatacontractJsonSerializer
[DataContract]
public class MyCustomClass
{
[DataMember]
public string foobar { get; set; }
}
[DataContract]
public class ContentItemViewModel
{
[DataMember]
public string CssClass { get; set; }
[DataMember]
public MyCustomClass PropertyB { get; set; }
}
class Program
{
static void Main(string[] args)
{
ContentItemViewModel model = new ContentItemViewModel();
model.CssClass = "StackOver";
model.PropertyB = new MyCustomClass();
model.PropertyB.foobar = "Flow";
//Create a stream to serialize the object to.
MemoryStream ms = new MemoryStream();
// Serializer the User object to the stream.
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ContentItemViewModel));
ser.WriteObject(ms, model);
byte[] json = ms.ToArray();
ms.Close();
string s= Encoding.UTF8.GetString(json, 0, json.Length);
Console.ReadLine();
}
}
Add all possible classes to DatacontractJsonSerializer.KnownTypes if MyCustomClass has derivations.
For whatever it may be worth after all this time, but I stumbled over the same problem and the solution is that the Deserializer hasn't got a clue about the classes you are deserializing unless you give him the necessary information.
On the top level, it knows the type from the type parameter of Deserialize<>(). That's why your converter for ContentItemViewModel works. For nested objects, it needs __type properties and a JavaScriptTypeResolver.
var ser = new JavaScriptSerializer(new SimpleTypeResolver());
ser.RegisterConverters(myconverters);
MyClass myObject = new MyClass();
string json = ser.Serialize(myObject);
// set a breakpoint here to see what has happened
ser.Deserialize<MyClass>(json);
A TypeResolver adds a __type property to each serialized object. You can write a custom type resolver that uses short names. In this sample, I use the SimpleTypeResolver from .net that "simply" stores the fully qualified type name as __type. When deserializing, the JavaScriptDeserializer finds __type and asks the TypeResolver for the correct type. Then it knows a type and can call a registered JavaScriptConverter.Deserialize method.
Without a TypeResolver, objects are deserialized to a Dictionary because JavaScriptSerializer doesn't have any type information.
If you can't provide a __type property in your json string, I think you'll need to deserialize to Dictionary first and then add a "guessing-step" that interprets the fields to find the right type. Then, you can use the ConvertToType method of JavaScriptSerializer to copy the dictionary into the object's fields and properties.
If you need to use the JavaScriptSerializer that is provides by ASP.NET and can't create your own, consider this section from the .ctor help of JavaScriptSerializer:
The instance of JavaScriptSerializer that is used by the asynchronous communication layer for invoking Web services from client script uses a special type resolver. This type resolver restricts the types that can be deserialized to those defined in the Web service’s method signature, or the ones that have the GenerateScriptTypeAttribute applied. You cannot modify this built-in type resolver programmatically.
Perhaps the GenerateScriptType Attribute can help you. But I don't know what kind of __type Properties are be needed here.
A base project contains an abstract base class Foo. In separate client projects, there are classes implementing that base class.
I'd like to serialize and restore an instance of a concrete class by calling some method on the base class:
// In the base project:
public abstract class Foo
{
abstract void Save (string path);
abstract Foo Load (string path);
}
It can be assumed that at the time of deserialization, all needed classes are present. If possible in any way, the serialization should be done in XML. Making the base class implement IXmlSerializable is possible.
I'm a bit stuck here. If my understanding of things is correct, then this is only possible by adding an [XmlInclude(typeof(UnknownClass))] to the base class for every implementing class - but the implementing classes are unknown!
Is there a way to do this? I've got no experience with reflection, but i also welcome answers using it.
Edit: The problem is Deserializing. Just serializing would be kind of easy. :-)
You can also do this at the point of creating an XmlSerializer, by providing the additional details in the constructor. Note that it doesn't re-use such models, so you'd want to configure the XmlSerializer once (at app startup, from configuration), and re-use it repeatedly... note many more customizations are possible with the XmlAttributeOverrides overload...
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
static class Program
{
static readonly XmlSerializer ser;
static Program()
{
List<Type> extraTypes = new List<Type>();
// TODO: read config, or use reflection to
// look at all assemblies
extraTypes.Add(typeof(Bar));
ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray());
}
static void Main()
{
Foo foo = new Bar();
MemoryStream ms = new MemoryStream();
ser.Serialize(ms, foo);
ms.Position = 0;
Foo clone = (Foo)ser.Deserialize(ms);
Console.WriteLine(clone.GetType());
}
}
public abstract class Foo { }
public class Bar : Foo {}
You don't have to put the serialization functions into any base class, instead, you can add it to your Utility Class.
e.g. ( the code is for example only, rootName is optional )
public static class Utility
{
public static void ToXml<T>(T src, string rootName, string fileName) where T : class, new()
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8);
serializer.Serialize(writer, src);
writer.Flush();
writer.Close();
}
}
Simply make call to
Utility.ToXml( fooObj, "Foo", #"c:\foo.xml");
Not only Foo's family types can use it, but all other serializable objects.
EDIT
OK full service... (rootName is optional)
public static T FromXml<T>(T src, string rootName, string fileName) where T : class, new()
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
TextReader reader = new StreamReader(fileName);
return serializer.Deserialize(reader) as T;
}
Well the serialization shouldn't be a problem, the XmlSerializer constructor takes a Type argument, even calling GetType on an instance of a derived class through a method on the abstract base will return the derived types actual Type. So in essence as long as you know the proper type upon deserialization then the serialization of the proper type is trivial. So you can implement a method on the base called serialize or what have you that passes this.GetType() to the constructor of the XmlSerializer.. or just passes the current reference out and lets the serialize method take care of it and you should be fine.
Edit: Update for OP Edit..
If you don't know the type at deserialization then you really have nothing but a string or byte array, without some sort of identifier somewhere you are kind of up a creek. There are some things you can do like trying to deserialize as every known derived type of the xx base class, I would not recommend this.
Your other option is to walk the XML manually and reconstruct an object by embedding the type as a property or what have you, maybe that is what you originally meant in the article, but as it stands I don't think there is a way for the built in serialization to take care of this for you without you specifying the type.
Somewhere deep inside the XML namespaces lies a wonderful class called XmlReflectionImporter.
This may be of help to you if you need to create a schema at runtime.
You can also do this by creating an XmlSerializer passign in all possible types to the constructor. Be warned that when you use this constructor the xmlSerializer will be compiled each and every time and will result in a leak if you constantly recreate it. You will want to create a single serializer and reuse it in your application.
You can then bootstrap the serializer and using reflection look for any descendants of foo.
These links will probably be helpful to you:
CodeProject article
Blog post
Stack Overflow question
I have a complex remoting project and wanted very tight control over the serialized XML. The server could receive objects that it had no idea how to deserialize and vice versa, so I needed a way to identify them quickly.
All of the .NET solutions I tried lacked the needed flexibility for my project.
I store an int attribute in the base xml to identify the type of object.
If I need to create a new object from xml I created a factory class that checks the type attribute then creates the appropriate derived class and feeds it the xml.
I did something like this (pulling this out of memory, so syntax may be a little off):
(1) Created an interface
interface ISerialize
{
string ToXml();
void FromXml(string xml);
};
(2) Base class
public class Base : ISerialize
{
public enum Type
{
Base,
Derived
};
public Type m_type;
public Base()
{
m_type = Type.Base;
}
public virtual string ToXml()
{
string xml;
// Serialize class Base to XML
return string;
}
public virtual void FromXml(string xml)
{
// Update object Base from xml
}
};
(3) Derived class
public class Derived : Base, ISerialize
{
public Derived()
{
m_type = Type.Derived;
}
public override virtual string ToXml()
{
string xml;
// Serialize class Base to XML
xml = base.ToXml();
// Now serialize Derived to XML
return string;
}
public override virtual void FromXml(string xml)
{
// Update object Base from xml
base.FromXml(xml);
// Update Derived from xml
}
};
(4) Object factory
public ObjectFactory
{
public static Base Create(string xml)
{
Base o = null;
Base.Type t;
// Extract Base.Type from xml
switch(t)
{
case Base.Type.Derived:
o = new Derived();
o.FromXml(xml);
break;
}
return o;
}
};
This method reads the XML root element and checks if the current executing assembly contains a type with such a name. If so, the XML document is deserialized. If not, an error is thrown.
public static T FromXml<T>(string xmlString)
{
Type sourceType;
using (var stringReader = new StringReader(xmlString))
{
var rootNodeName = XElement.Load(stringReader).Name.LocalName;
sourceType =
Assembly.GetExecutingAssembly().GetTypes()
.FirstOrDefault(t => t.IsSubclassOf(typeof(T))
&& t.Name == rootNodeName)
??
Assembly.GetAssembly(typeof(T)).GetTypes()
.FirstOrDefault(t => t.IsSubclassOf(typeof(T))
&& t.Name == rootNodeName);
if (sourceType == null)
{
throw new Exception();
}
}
using (var stringReader = new StringReader(xmlString))
{
if (sourceType.IsSubclassOf(typeof(T)) || sourceType == typeof(T))
{
var ser = new XmlSerializer(sourceType);
using (var xmlReader = new XmlTextReader(stringReader))
{
T obj;
obj = (T)ser.Deserialize(xmlReader);
xmlReader.Close();
return obj;
}
}
else
{
throw new InvalidCastException(sourceType.FullName
+ " cannot be cast to "
+ typeof(T).FullName);
}
}
}
Marking the classes as Serializable and using SoapBinaryFormatter instead of XmlSerializer will give you this functionality automatically. When serializing the type information of the instance being serialized will be written to the XML, and SoapBinaryFormatter can instantiate the subclasses when deserializing.
I used the XmlType attribute of the unknown (but expected) classes to determine the Type for the deserialization. The to be expected types are load during the instantiation of the AbstractXmlSerializer class and placed in a dictionary. During the deserialization the root element is read and with this the type is retrieved form the dictionary. After that it can be deserialized normally.
XmlMessage.class:
public abstract class XmlMessage
{
}
IdleMessage.class:
[XmlType("idle")]
public class IdleMessage : XmlMessage
{
[XmlElement(ElementName = "id", IsNullable = true)]
public string MessageId
{
get;
set;
}
}
AbstractXmlSerializer.class:
public class AbstractXmlSerializer<AbstractType> where AbstractType : class
{
private Dictionary<String, Type> typeMap;
public AbstractXmlSerializer(List<Type> types)
{
typeMap = new Dictionary<string, Type>();
foreach (Type type in types)
{
if (type.IsSubclassOf(typeof(AbstractType))) {
object[] attributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), false);
if (attributes != null && attributes.Count() > 0)
{
XmlTypeAttribute attribute = attributes[0] as XmlTypeAttribute;
typeMap[attribute.TypeName] = type;
}
}
}
}
public AbstractType Deserialize(String xmlData)
{
if (string.IsNullOrEmpty(xmlData))
{
throw new ArgumentException("xmlData parameter must contain xml");
}
// Read the Data, Deserializing based on the (now known) concrete type.
using (StringReader stringReader = new StringReader(xmlData))
{
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
String targetType = GetRootElementName(xmlReader);
if (targetType == null)
{
throw new InvalidOperationException("XML root element was not found");
}
AbstractType result = (AbstractType)new
XmlSerializer(typeMap[targetType]).Deserialize(xmlReader);
return result;
}
}
}
private static string GetRootElementName(XmlReader xmlReader)
{
if (xmlReader.IsStartElement())
{
return xmlReader.Name;
}
return null;
}
}
UnitTest:
[TestMethod]
public void TestMethod1()
{
List<Type> extraTypes = new List<Type>();
extraTypes.Add(typeof(IdleMessage));
AbstractXmlSerializer<XmlMessage> ser = new AbstractXmlSerializer<XmlMessage>(extraTypes);
String xmlMsg = "<idle></idle>";
MutcMessage result = ser.Deserialize(xmlMsg);
Assert.IsTrue(result is IdleMessage);
}