Given the following XML I want to deserialize:
<?xml version="1.0" encoding="utf-8" ?>
<units>
<entity>
<health max="1000"/>
<sprite texture="tank"/>
<entity>
<sprite texture="tank-turret"/> <!-- this element is missing when i deserialize --!>
</entity>
</entity>
</units>
How can I deserialize this recursive object graph using XmlSerializer?
The following is my last try. It successfully deserializes the top-level objects (health, sprite, entity) but it does not seem to find the sprite element in the nested entity node.
I also tried deriving entity from componentlist, but it didn't work either.
public class UnitSerializer
{
public abstract class item
{
}
public class entity : item
{
[XmlArray("entity")]
[XmlArrayItem(typeof(health))]
[XmlArrayItem(typeof(entity))]
[XmlArrayItem(typeof(sprite))]
public componentlist entity2 { get; set; }
}
public abstract class component : item
{
}
public class health : component
{
[XmlAttribute]
public int max { get; set; }
}
public class sprite : component
{
[XmlAttribute]
public string texture { get; set; }
}
public class componentlist : List<item>
{
}
[XmlRoot("units")]
public class units
{
[XmlArray("entity")]
[XmlArrayItem(typeof(health))]
[XmlArrayItem(typeof(entity))]
[XmlArrayItem(typeof(sprite))]
public componentlist entity { get; set; }
}
public void Read()
{
var x = new XmlSerializer(typeof(units),
new[] {
typeof(componentlist),
typeof(entity),
typeof(health),
typeof(sprite)
});
var fs = new FileStream("units.xml", FileMode.Open);
XmlReader reader = new XmlTextReader(fs);
var units = (units)x.Deserialize(reader);
}
}
Your classes can be fixed by replacing use of [XmlArray] and [XmlArrayItem] with [XmlElement(typeof(TDerived))]:
public class UnitSerializer
{
public abstract class item
{
}
public class entity : item
{
[XmlElement("health", typeof(health))]
[XmlElement("entity", typeof(entity))]
[XmlElement("sprite", typeof(sprite))]
public List<item> EntityList { get; set; }
}
public abstract class component : item
{
}
public class health : component
{
[XmlAttribute]
public int max { get; set; }
}
public class sprite : component
{
[XmlAttribute]
public string texture { get; set; }
}
[XmlRoot("units")]
public class units
{
[XmlElement("health", typeof(health))]
[XmlElement("entity", typeof(entity))]
[XmlElement("sprite", typeof(sprite))]
public List<item> EntityList { get; set; }
}
public units Read(string filename)
{
var x = new XmlSerializer(typeof(units));
using (var fs = new FileStream(filename, FileMode.Open))
using (var reader = XmlReader.Create(fs))
{
return (units)x.Deserialize(reader);
}
}
}
Notes:
[XmlArray] indicates a collection should be serialized with an outer wrapper element containing a sequence of elements, while [XmlElement] indicates a collection should be serialized as a sequence without the wrapper. Your XML sample uses repeating elements without wrapper elements, so [XmlElement] should be used. It sort of works because your XML is recursive -- but at every other level repeating elements are getting incorrectly deserialized as wrappers. This explains why some but not all data is lost during deserialization.
In your XML sample, polymorphic elements are identified by element name. The XmlSerializer(Type, Type[]) constructor should be used to specify polymorphic included types to be serialized using the xsi:type mechanism. Since the xsi:type attribute does not appear in your XML, this constructor need not be used.
(In addition, when constructing an XmlSerializer using the XmlSerializer(Type, Type[]) constructor, you must cache the serializer statically to avoid a severe memory leak. See Memory Leak using StreamReader and XmlSerializer for why.)
XmlTextReader has been deprecated since .Net 2.0. Use XmlReader.Create() instead.
The FileStream and XmlReader should be disposed, ideally via using statements.
I eliminated the public class componentlist : List<item> and replaced it with just a List<item>. This was mainly a matter of taste, but it does make it easier to set the value of such a list using Linq's .ToList().
Demo fiddle here.
Related
I want to create an generic abstract type as base for my xml serialized types:
public abstract class RootElementBase<TEelment>
{
public IList<TElement> SubElements {get;set;}
public RootElementBase(){
SubElements = new List<T>();
}
}
And I will use like this:
[XmlRoot(ElementName = "myroot")]
public class MyRoot: RootElementBase<ItemType> {
[XmlElement("item")]
public override List<ItemType> Elements { get; set; }
}
But this does not serialize MyRoot class. Implemented types use generic abstract class for Elements type. But XmlElelemt attribute tags will be set.
Serialization and deserialization of derived types is supported. The following attributes control the Xml Serialization:
[XmlElement]
[XmlAttribute]
[XmlIgnore]
We need to instruct the Xml serializer to ignore the base class members that we wish to work with in our concrete derived types.
See -> https://learn.microsoft.com/en-us/dotnet/standard/serialization/attributes-that-control-xml-serialization
Also, be-careful with virtual member calls in the constructor
See -> https://msdn.microsoft.com/en-us/library/ms182331.aspx
Give this a shot:
Solution 1
Using the derived class MyRoot as the type parameter for the XmlSerializer
Abstract base class:
[Serializable]
public abstract class RootElementBase<TEelment>
{
[XmlIgnore]
public virtual List<TEelment> SubElements { get; set; }
protected RootElementBase()
{
SubElements = new List<TEelment>();
}
}
Concrete Class:
[XmlRoot(ElementName = "myroot")]
public class MyRoot : RootElementBase<ItemType>
{
[XmlElement("item")]
public override List<ItemType> SubElements { get; set; }
}
Dummy ItemType Class:
public class ItemType
{
public string Name { get; set; }
}
This will output the following:
<?xml version="1.0"?>
<myroot xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<Name>Jim</Name>
</item>
<item>
<Name>Ben</Name>
</item>
<item>
<Name>Tom</Name>
</item>
</myroot>
Test Console App:
class Program
{
static void Main(string[] args)
{
MyRoot root = new MyRoot();
root.SubElements.Add(new ItemType() { Name = "Jim"});
root.SubElements.Add(new ItemType() { Name = "Ben" });
root.SubElements.Add(new ItemType() { Name = "Tom" });
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyRoot));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, root);
Console.WriteLine(stringWriter);
Console.ReadKey();
}
}
Solution 2
Using the abstract base class RootElementBase as the type parameter for the XmlSerializer with an XmlRoot override parameter
Per MSDN:
The root element of an XML document encloses all the other elements.
By default, the object specified by the type parameter is serialized
as the root element. Properties, such as the XML element name of the
root element are taken from the type object. However, the root
parameter allows you to replace the default object's information by
specifying an XmlRootAttribute; the object allows you to set a
different namespace, element name, and so on.
https://msdn.microsoft.com/en-us/library/65k4wece(v=vs.110).aspx
Abstract Base Class:
[Serializable]
[XmlInclude(typeof(MyRoot))]
public abstract class RootElementBase<TEelment>
{
[XmlIgnore]
public virtual List<TEelment> SubElements { get; set; }
protected RootElementBase()
{
SubElements = new List<TEelment>();
}
}
Concrete Class:
[XmlRoot(ElementName = "myroot")]
public class MyRoot : RootElementBase<ItemType>
{
[XmlElement("item")]
public override List<ItemType> SubElements { get; set; }
}
Dummy ItemType Class:
public class ItemType
{
public string Name { get; set; }
}
Sample Console App with Generic Serialization
class Program
{
static void Main(string[] args)
{
MyRoot root = new MyRoot();
root.SubElements.Add(new ItemType() { Name = "Jim"});
root.SubElements.Add(new ItemType() { Name = "Ben" });
root.SubElements.Add(new ItemType() { Name = "Tom" });
string xml = Serialize(root, "myNewRoot");
Console.WriteLine(xml);
Console.ReadKey();
}
static string Serialize<TElement>(RootElementBase<TElement> tElement, string rootElementName)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(RootElementBase<TElement>),
new XmlRootAttribute(rootElementName));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, tElement);
return stringWriter.ToString();
}
}
Note - With This solution you need to be aware of Dynamically Generated Assemblies
Dynamically Generated Assemblies
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
Please see MSDN for Remarks: https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer(v=vs.110).aspx
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>
I have a class that I need to serialize/deserialize, and I'm half way there - I have serialization functional, resulting in the below XML. However, since I'm implementing IXmlSerializable myself, I'm uncertain what an implementation of ReadXml should look like, given that SomeGenericClass<T> was serialized using attribute-based flagging rather than an explicit implementation if IXmlSerializable
<?xml version="1.0" encoding="utf-16"?>
<FooContainer FooName="DoSomething">
<SomeGenericClassOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="Foobar" Name="firstParam" Description="First Paramater Serialized" />
<SomeGenericClassOfInt32 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="10000" Name="nextParam" Description="Second Serialized parameter" />
</FooContainer>
Which I want to serialize back into an instance of:
public class FooContainer : IList<ISomeGenericClassBase>, IXmlSerializable
{
public string FooName {get;set;}
void IXmlSerializable.WriteXml(XmlWriter writer) {
var serializer = XmlSerializer.FromTypes(new Type[]{SomeGenericBaseClass})[0];
this
.Select(item=>SomeGenericClassBase.ConvertToMe(item))
.ToList()
.ForEach(item=>serializer.Serialize(writer, item));
}
// IList Implementation omitted - wraps a private List<ISomeGenericClassBase>
}
Where the list will contain instances along these lines:
public interface ISomeGenericClassBase
{
}
public interface ISomeGenericBaseClass<T> : ISomeGenericBaseClass
{
}
public class SomeGenericClassBase : ISomeGenericClassBase
{
public static SomeGenericClassBase ConvertToMe(ISomeGenericClassBase target) {
return new SomeGenericClassBase() {Property1 = target.Property1; Property2 = target.Property2}
}
public static ISomeGenericBaseClass ExpantToTyped(SomeGenericClassBase target) {
// Implementation omitted - converts a base class instance to a generic instance by working out the generic type from saved data and reconstructing
}
}
public class SomeGenericClass<T> : SomeGenericClassBase, ISomeGenericBaseClass<T>
{
[XmlAttribute]
public string Name {get;set;}
[XmlAttribute]
public string Description{get;set;}
[XmlAttribute]
public T Value {get;set;}
[XmlElement("")]
public T[] ValidOptions {get;set;}
}
EDIT: Expanded the implementation - realised as it was, it didn't illustrate the problem correctly
Core issue is that I want to be able to serialize items that only implement the interface, even if I only get back SomeGenericClassBase instances. Per the approach used in the ExpandToTyped method, I'm expecting consumers of the class to save sufficient data in their implementations that allow the resulting classes to be converted back into their original form as required. So yes, there's a loss of fidelity, but it's one I can live with in exchange for the flexibility of using a list of interfaces instead of a list of base classes.
One solution is to sidestep the issue (IXmlSerializable.ReadXml looks quite painful anyway, e.g. for collections). What I eventually did is scrap IXmlSerializable, and instead generate a class along the lines of the below.
Please note that whilst this approach works, it's currently quite error prone if the serializable instance is used for anything other than serialization - synchronization is maintained ONLY when SerializationTarget is set or retrieved. When it's set, we convert existing parameters to appropriate instances and add them to a serializable list. When it's retrieved, if it's null, we inflate from whatever was in the current value.
However, if FooContainer changes after the creation of this object, it won't maintain that synchronization and what gets serialized will be out of date. This is largely because I'm lazy and don't want to implement IList<SomeGenericClassBase> again to override the Add and Remove methods (though this would be the more robust approach).
public class FooContainerSerializable
{
public FooContainerSerializable() {}
public FooContainerSerializable(FooContainer serializationTarget)
{
this.SerializationTarget = serializationTarget;
}
[XmlIgnore]
public FooContainer SerializationTarget
{
get {
if (_SerializationTarget == null)
{
_SerializationTarget = new FooContainer();
// Copy across extant collection properties here
this.Parameters.ForEach(item=>_SerializationTarget.Add(item));
}
return _SerializationTarget;
}
set {
// Synchronize this entity's entries here
_SerializationTarget = value;
_SerializationTarget.ForEach(item=>this.Parameters.Add(item.Deflate()));
}
}
private FooContainer _SerializationTarget;
[XmlElement]
public string FooName {
get {return this.SerializationTarget.FooName;}
set {this.SerializationTarget.FooName = value;}
}
[XmlElement]
public List<SomeGenericClassBase> Parameters {
get {return _Parameters ?? (_Parameters = new List<SomeGenericClassBase>());}
set {_Parameters = value;}
}
}
Here is another option if you are willing to use an abstract class instead of an interface in your collection definition. You'd also need to declare all the derived types of SomeGenericClassBase using XmlInclude attributes. I'm thinking this wouldn't be too bad if there are just a handful of types you'd use with this class.
[XmlRoot(ElementName = "FooContainer")]
public class FooContainer : List<SomeGenericClassBase>
{
[XmlAttribute]
public string FooName { get; set; }
}
[XmlInclude(typeof(SomeGenericClass<string>))]
[XmlInclude(typeof(SomeGenericClass<int>))]
public abstract class SomeGenericClassBase
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Description { get; set; }
}
public class SomeGenericClass<T> : SomeGenericClassBase
{
[XmlAttribute]
public T Value { get; set; }
[XmlElement]
public T[] ValidOptions { get; set; }
}
class Class1
{
public static void Run()
{
var f = new FooContainer()
{
new SomeGenericClass<string> { Name = "firstParam", Description = "First Paramater Serialized", Value = "Foobar"},
new SomeGenericClass<int> { Name = "nextParam", Description = "Second Serialized parameter", Value = 10000 }
};
f.FooName = "DoSomething";
XmlSerializer serializer = new XmlSerializer(f.GetType());
StringBuilder sb = new StringBuilder();
// Serialize
using (StringWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, f);
}
Console.WriteLine(sb);
// Deserialize
using(StringReader reader = new StringReader(sb.ToString()))
{
FooContainer f2 = (FooContainer)serializer.Deserialize(reader);
}
}
}
This would serialize to the following XML:
<?xml version="1.0" encoding="utf-16"?>
<FooContainer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SomeGenericClassBase xsi:type="SomeGenericClassOfString" Name="firstParam" Description="First Paramater Serialized" Value="Foobar" />
<SomeGenericClassBase xsi:type="SomeGenericClassOfInt32" Name="nextParam" Description="Second Serialized parameter" Value="10000" />
</FooContainer>
Deserialization maintains full fidelity.
I want to create a method that serializes lists of different childs. Each child gets his own XML file. Doing this for a single object was easy:
interface ISerializable
{
}
public class Item : ISerializable
{
public string name { get; set; }
public int number { get; set; }
}
public class Weapon : ISerializable
{
public string name { get; set; }
public int damage {get; set;}
}
public static void SerializeToXML(ISerializable child)
{
XmlSerializer serializer = new XmlSerializer(child.GetType());
using (TextWriter textWriter = new StreamWriter("test.xml"))
{
serializer.Serialize(textWriter, child);
}
}
I can put anything derived from ISerializable into the serialize method to get a desired result. However when give List<ISerializable> as possible argument it does not compile Cannot convert List<item> to List<ISerializable>. Anyway to solve this?
public static void SerializeListToXML(List<ISerializable> listToXML)
{
XmlSerializer serializer = new XmlSerializer(listToXML.GetType());
using (TextWriter textWriter = new StreamWriter("test.xml"))
{
serializer.Serialize(textWriter, listToXML);
}
}
The base reason i'm doing this is to get XML templates of much larger structures so i an manually add XML to load into my program. I figured i would benefit from creating a serializer first so i have the correct XML structure to start with. Perhaps this can come in handy later down the line for saving user data too.
SerializeListToXML should take an IEnumerable<ISerializable> instead of a List<Item>.
A List<Item> cannot be casted to List<ISerializable>. If you could to that, then you'd be able to add any ISerializable to the list, not just Items. With an IEnumerable<>, you cannot modify the collection - only iterate over it. Since SerializeListToXML does not need to modify the collection, it can accept an IEnumerable<ISerializable>, which is less restrictive. Please see the following section and sub-sections about covariance and contravariance: Covariance and Contravariance
I have 2 derived classes that will be serialized into xml.
While the code works fine (XmlSerializer, nothing strange), the serialization of DataScenario causes its MyData property items to produce Xmlelement names from the base class name:
<DataScenario>
<MyData>
<ScenarioData/>
<ScenarioData/>
<ScenarioData/>
</MyData>
<DataScenario>
Instead, i'm trying to have these items produce XmlElement names from their derived classes
<DataScenario>
<MyData>
<type1/>
<type1/>
<type2/>
</MyData>
<DataScenario>
Is this even possible? Keep in mind I need to deserialize as well; I'm unsure whether the Deserialize process will understand that derived objects need to be created.
Sample code i'm using is as follows.
[Serializable]
[XmlInclude(typeof(Type1))]
[XmlInclude(typeof(Type2))]
public class Scenario
{
[XmlElement("location")]
public string Location { get; set; }
[XmlElement("value")]
public string Value { get; set; }
public Scenario()
{
}
}
[Serializable]
[XmlType("type1")]
public class Type1 : Scenario
{
public FillPointData() : base() { }
}
[Serializable]
[XmlType("type2")]
public class Type2 : Scenario
{
public TestData() : base() { }
}
//Hosting class of all scenarios
public DataScenario()
{
public List<Scenario> MyData{ get; set; }
}
You can define what kind of Elements are in the Collection with the XmlArrayItem attribute.
If the Type is known (defined as you did with the XmlInclude attribute) it will create Tags "Type1", "Type2". If the Types are not known, it will still create a Tag called ScenarioData with an Attribute xsi:type="Type1" which is used to map the type while deserialization.
[XmlArrayItem(typeof(Type1))]
[XmlArrayItem(typeof(Type2))]
Public List<Scenario> Children
{
// getter & setter
}