Reputation: 1626
I have a class that I want a custom serializer for. And I will sometimes use this class within a List, that I also want to serialize. Some of those elements will be null.
I can get serialization working:
<MyData xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Fractions>
<Frac>1/2</Frac>
<Frac xsi:nil="true" />
<Frac xsi:nil="true" />
<Frac>3/6</Frac>
</Fractions>
</MyData>
But de-serialization is not working when null elements are present like above. The List<> serializer appears to be invoking ReadXml() for a element vs. just creating a null element in the list.
When I run my example, the de-serialized version is:
1/2
/
/
3/6
I.e. a MyFrac object was created instead of null in element 1 and 2.
Do I have to create a custom serializer for a List sub-class to get around this, or am I missing some other way to get null elements on de-serialize? If custom serializer, any best approach/code?
I have a complete example below which shows my current implementation.
public class MyFrac : IXmlSerializable
{
public string N;
public string D;
public override string ToString()
{
return N + "/" + D;
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
if (reader.IsEmptyElement && reader.NodeType != XmlNodeType.EndElement)
{
reader.Read();
return;
}
reader.ReadStartElement();
string sfrac = reader.ReadString();
try
{
var m = Regex.Match(sfrac, @"(\d+)/(\d+)");
if (!m.Success)
throw new Exception(sfrac + " was not in the correct format");
N = m.Result("$1");
D = m.Result("$2");
}
finally
{
reader.ReadEndElement();
}
}
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteString(N + "/" + D);
}
}
public class MyData
{
[XmlArrayItem("Frac")]
public List<MyFrac> Fractions;
}
public static void Run()
{
var data = new MyData();
data.Fractions = new List<MyFrac>();
data.Fractions.Add(new MyFrac { N = "1", D = "2" });
data.Fractions.Add(null);
data.Fractions.Add(null);
data.Fractions.Add(new MyFrac { N = "3", D = "6" });
var serializer = new XmlSerializer(typeof(MyData));
StringBuilder sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
serializer.Serialize(writer, data);
}
// Dump XML
Console.WriteLine(sb.ToString());
using (var reader = new StringReader(sb.ToString()))
{
var data2 = (MyData)serializer.Deserialize(reader);
Console.WriteLine(data2.Fractions[0]);
Console.WriteLine(data2.Fractions[1]);
Console.WriteLine(data2.Fractions[2]);
Console.WriteLine(data2.Fractions[3]);
}
}
Upvotes: 2
Views: 840
Reputation: 1136
Your problem occurs because of during deserialization XmlSerializer first create an object by calling default constructor and then call ReadXml method to set property values so ReadXml can't cancel object creation. Is it necessary to serialize null values to see it in xml? I mean that you may avoid this by using non-List collection. For example create your custom collection:
public class MyCollection : System.Collections.ObjectModel.Collection<MyFrac>
{
protected override void InsertItem(int index, MyFrac item)
{
if(item == null) return;
base.InsertItem(index, item);
}
protected override void SetItem(int index, MyFrac item)
{
if(item == null)
{
base.RemoveAt(index);
}
else
{
base.SetItem(index, item);
}
}
}
Use it in your MyData class:
public class MyData
{
[XmlArrayItem("Frac")]
public MyCollection Fractions;
}
And then serialization/deserialization works as you want:
public static void Main(string[] args)
{
var data = new MyData();
data.Fractions = new MyCollection();
data.Fractions.Add(new MyFrac { N = "1", D = "2" });
data.Fractions.Add(null);
data.Fractions.Add(null);
data.Fractions.Add(new MyFrac { N = "3", D = "6" });
var serializer = new XmlSerializer(typeof(MyData));
StringBuilder sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
serializer.Serialize(writer, data);
}
// Dump XML
Console.WriteLine(sb.ToString());
using (var reader = new StringReader(sb.ToString()))
{
var data2 = (MyData)serializer.Deserialize(reader);
foreach (var element in data2.Fractions) {
Console.WriteLine(element);
}
}
Console.ReadLine();
}
Serialized xml:
<?xml version="1.0" encoding="utf-16"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Fractions>
<Frac>1/2</Frac>
<Frac>3/6</Frac>
</Fractions>
</MyData>
Outputs:
1/2
3/6
Update
Ok, you need a colletion with custom serialization rules. Let's implement it:
public class MyCollection<T> : Collection<T>, IXmlSerializable where T: class
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var serializer = new XmlSerializer(typeof(T));
var wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.IsEmptyElement)
{
reader.Read();
Items.Add(null);
continue;
}
var item = (T)serializer.Deserialize(reader);
Items.Add(item);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
var serializer = new XmlSerializer(typeof (T));
foreach (var myFrac in Items)
{
serializer.Serialize(writer, myFrac);
}
}
}
Usage:
public class MyData
{
public MyCollection<MyFrac> Fractions;
}
class Program
{
static void Main(string[] args)
{
var data = new MyData();
data.Fractions = new MyCollection<MyFrac>();
data.Fractions.Add(new MyFrac { N = "1", D = "2" });
data.Fractions.Add(null);
data.Fractions.Add(null);
data.Fractions.Add(new MyFrac { N = "3", D = "6" });
var serializer = new XmlSerializer(typeof(MyData));
StringBuilder sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
serializer.Serialize(writer, data);
}
// Dump XML
Console.WriteLine(sb.ToString());
Trace.WriteLine(sb.ToString());
using (var reader = new StringReader(sb.ToString()))
{
var data2 = (MyData)serializer.Deserialize(reader);
foreach (var fraction in data2.Fractions)
{
var output = fraction == null ? "null" : fraction.ToString();
Console.WriteLine(output);
Trace.WriteLine(output);
}
}
Console.ReadLine();
}
}
Output xml:
<?xml version="1.0" encoding="utf-16"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Fractions>
<MyFrac>1/2</MyFrac>
<MyFrac xsi:nil="true" />
<MyFrac xsi:nil="true" />
<MyFrac>3/6</MyFrac>
</Fractions>
</MyData>
Output data:
1/2
null
null
3/6
I think this is what you want.
Upvotes: 2