Reputation: 247
Here's my problem. I have a parent class "Foo" and a child class "Bar":
[Serializable]
public class Foo, IXmlSerializable
{
public Bar Child {get; set;}
#region IXmlSerializable Membres
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
new XmlSerializer(this.Child.GetType()).Serialize(writer, this.Child);
}
#endregion
}
[Serializable]
public class Bar
{
[XmlElement]
public string MyElement1 {get; set;}
[XmlElement]
public string MyElement2 {get; set;}
}
If I serialize these classes as is, I'm going to get something like this:
<xml>
<Foo>
<Bar>
<MyElement1>beer</MyElement>
<MyElement2>vodka</MyElement>
</Bar>
</Foo>
How can I control the serialization from the "Foo" (parent) class to remove the "Bar" node? I want to have something like this:
<xml>
<Foo>
<MyElement1>beer</MyElement>
<MyElement2>vodka</MyElement>
</Foo>
This sample is voluntarily very simple. Thanks for any help!
Upvotes: 0
Views: 1447
Reputation: 371
The trick here is to use both mechanisms to do the serialisation and deserialisation.
In the below example I have a List of type Foo that is IXmlSerializable that has an abstract child. Each concrete child implements serialisation using attributes which is much more tidy.
For writing, Foos WriteXml writes out an element whose name is the full type name of the contract child. It then serialises the child.
For Reading, Foos ReadXml does some ugly reads to position itself at the point where the full name is, and creates a new instance of the concrete type (but note that Foo doesn't know about all the possible concrete types) It then creates a new XmlSerializer based on the concrete type and deserialises it. Again it needs to do a couple of ugly reads to position the reader for the next element.
This produces the following XML
<?xml version="1.0"?>
<ArrayOfFoo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Foo>
<ParentData>a</ParentData>
<XmlAbstractSerialisationTest.Bar1>
<Bar1>
<Id>0</Id>
<Name>hello</Name>
<Extra1>boo</Extra1>
<Extra2>good</Extra2>
</Bar1>
</XmlAbstractSerialisationTest.Bar1>
</Foo>
<Foo>
<ParentData>b</ParentData>
<XmlAbstractSerialisationTest.Bar2>
<Bar2>
<Id>0</Id>
<Name>hello</Name>
<Extra1>boo</Extra1>
<Extra2>123</Extra2>
</Bar2>
</XmlAbstractSerialisationTest.Bar2>
</Foo>
</ArrayOfFoo>
The code is:
namespace XmlAbstractSerialisationTest
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
class Program
{
static void Main(string[] args)
{
// Write
List<Foo> data = new List<Foo>{
new Foo{
ParentData = "a",
Child = new Bar1{
Id = 0,
Name = "hello",
Extra1 = "boo",
Extra2 = "good"
}
},
new Foo{
ParentData = "b",
Child = new Bar2{
Id = 0,
Name = "hello",
Extra1 = "boo",
Extra2 = 123
}
}
};
XmlSerializer xs = new XmlSerializer(typeof(List<Foo>));
using (FileStream fs = new FileStream("test.xml", FileMode.Create, FileAccess.Write))
{
xs.Serialize(fs, data);
}
// Read
List<Foo> newData;
using (FileStream fs = new FileStream("test.xml", FileMode.Open, FileAccess.Read))
{
newData = xs.Deserialize(fs) as List<Foo>;
}
}
}
public class Foo : IXmlSerializable
{
public string ParentData { get; set; }
public BaseBar Child { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new System.NotImplementedException();
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
reader.Read();
ParentData = reader.Value;
reader.Read();
reader.Read();
Assembly ass = Assembly.LoadFile(@"[Full path to assembly]");
Child = ass.CreateInstance(reader.Name) as BaseBar;
reader.Read();
XmlSerializer xs = new XmlSerializer(Child.GetType());
Child = xs.Deserialize(reader) as BaseBar;
reader.Read();
reader.Read();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("ParentData", ParentData);
writer.WriteStartElement(Child.GetType().FullName);
XmlSerializer xs = new XmlSerializer(Child.GetType());
xs.Serialize(writer, Child);
writer.WriteEndElement();
}
}
[Serializable]
public abstract class BaseBar
{
[XmlElement]
public int Id { get; set; }
[XmlElement]
public string Name { get; set; }
}
[Serializable]
public class Bar1 : BaseBar
{
[XmlElement]
public string Extra1 { get; set; }
[XmlElement]
public string Extra2 { get; set; }
}
[Serializable]
public class Bar2 : BaseBar
{
[XmlElement]
public string Extra1 { get; set; }
[XmlElement]
public int Extra2 { get; set; }
}
}
The above code can serialise and deserialise arbitrarilily complicated concrete types while the parent class doesn't know about those types. Each time you deal with a concrete type you need to serialise out the full name. And then the deserialisation must have knowledge of the full name (which is encoded into the element name) and the assembly where the type can be found.
Problems with the above code! If the concrete types have non-default constructors then the CreateInstance method will fail. Possibly it could use FormatterServices.GetUninitializedObject I'll have a play with that tomorrow.
This code should work, but you'll need to update "[Full path to assembly]" to the correct assembly path.
Upvotes: 0
Reputation: 2856
I'm not sure what circumstances would lead you to require this, it doesn't really feel right, but for now I'll just assume you have a valid reason.
If you implement IXmlSerializable on Bar and have Foo proxy through to it instead of using a XmlSerializer then this should get you what you want ... it just means you loose the convenience of using serialisation attributes on Bar.
Upvotes: 0
Reputation: 247
@Marc : It's complicated to explain, I just need it...
I think I found a way. Maybe not the best one but not the worst too... Thanks for your help !
public void WriteXml(System.Xml.XmlWriter writer)
{
if (this.Child != null)
{
XmlSerializer xs = new XmlSerializer(typeof(Bar));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "http://www.w3.org/2001/XMLSchema-instance");
ns.Add("", "http://www.w3.org/2001/XMLSchema");
XElement res = SerializeAsXElement(xs, this.Child, ns);
MoveDescendants(res, writer);
// new XmlSerializer(this.InitRes.GetType()).Serialize(writer, this.InitRes);
}
}
#endregion
/// <summary>
/// Moves all the children of src (including all its elements and attributes) to the
/// destination element, dst.
/// </summary>
/// <param name="src">The source element.</param>
/// <param name="dst">The destination element.</param>
public static void MoveDescendants(XElement src, XmlWriter dst)
{
foreach (XAttribute attr in src.Attributes())
{
dst.WriteAttributeString(attr.Value, attr.Name.LocalName);
}
foreach (XNode elem in src.Nodes())
{
elem.WriteTo(dst);
}
}
public static XElement SerializeAsXElement(XmlSerializer xs, object o, XmlSerializerNamespaces ns)
{
XDocument d = new XDocument();
using (XmlWriter w = d.CreateWriter()) xs.Serialize(w, o, ns);
XElement e = d.Root;
e.Remove();
return e;
}
Upvotes: 0
Reputation: 754518
You need to change the Foo
's WriteXml
method and do something like this:
public void WriteXml(System.Xml.XmlWriter writer)
{
//new XmlSerializer(this.Child.GetType()).Serialize(writer, this.Child);
writer.WriteElementString("MyElement1", this.Child.MyElement1);
writer.WriteElementString("MyElement2", this.Child.MyElement2);
}
This would render out the XML you're looking for (basically make the <Bar>
node vanish).
Upvotes: 1