In one of my objects I have a dictionary that is serialized by implementing IXmlSerializable
:
public SerializableStringDictionary Parameters { get; set; }
When I serialize a list of these object with the normal .NET serializer it serializes fine, however deserialization is only doing a single object - elements in the XML file that follow the serializable dictionary are getting skipped.
For example, I have a list of <MaintenanceIndicators>
. I show only one below, but this is a List<MaintenanceIndicator>
in the code. The serialization of a list with multiple entries works fine but deserializing multiple only gives me 1 MaintenanceIndicator
in the List. Its parameters property however is deserialized ok. No exceptions are thrown in the code.
I use the following code for deserialization:
public void ReadXml(XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
// jump to <parameters>
reader.Read();
if (wasEmpty)
return;
// read until we reach the last element
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
// jump to <item>
reader.MoveToContent();
// jump to key attribute and read
reader.MoveToAttribute("key");
string key = reader.GetAttribute("key");
// jump to value attribute and read
reader.MoveToAttribute("value");
string value = reader.GetAttribute("value");
// add item to the dictionary
this.Add(key, value);
// jump to next <item>
reader.ReadStartElement("item");
reader.MoveToContent(); // workaround to trigger node type
}
}
My structure in XML looks as follows:
<MaintenanceIndicators>
<MaintenanceIndicator Id="1" Name="Test" Description="Test" Type="LimitMonitor" ExecutionOrder="1">
<Parameters>
<item key="Input" value="a" />
<item key="Output" value="b" />
<item key="Direction" value="GreaterThan" />
<item key="LimitValue" value="1" />
<item key="Hysteresis" value="1" />
</Parameters>
</MaintenanceIndicator>
<!-- Any subsequent MaintenanceIndicator indicator element will not be read -->
</MaintenanceIndicators>
Your question is doesn't contain a complete example of your problem. To confirm, you have the following (simplified) class definitions, with SerializableStringDictionary
as shown in your question:
public class MaintenanceIndicator
{
public MaintenanceIndicator()
{
this.Parameters = new SerializableStringDictionary();
}
public SerializableStringDictionary Parameters { get; set; }
}
[XmlRoot("MaintenanceIndicators")]
public class MaintenanceIndicators
{
[XmlElement("MaintenanceIndicator")]
public List<MaintenanceIndicator> MaintenanceIndicatorList { get; set; }
}
And the following XML:
<MaintenanceIndicators> <MaintenanceIndicator> <Parameters> <item key="Input" value="a" /> <item key="Output" value="b" /> </Parameters> </MaintenanceIndicator> <MaintenanceIndicator> <Parameters> <item key="Input2" value="a" /> <item key="Output2" value="b" /> </Parameters> </MaintenanceIndicator> </MaintenanceIndicators>
In this case, using your ReadXml()
, I was able to reproduce that, when reading the parsing above, only the first <MaintenanceIndicator>
element was deserialized.
Your problem is that, in ReadXml()
, you do not consume the </Parameters>
end node. From the docs:
When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
Failing to call reader.Read();
at the end of ReadXml()
to read the element end will cause all subsequent elements in the XML to be skipped or otherwise read wrongly.
Thus you should modify ReadXml()
as follows:
public void ReadXml(XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
// jump to <parameters>
reader.Read();
if (wasEmpty)
return;
// read until we reach the last element
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
// jump to <item>
reader.MoveToContent();
// jump to key attribute and read
string key = reader.GetAttribute("key");
// jump to value attribute and read
string value = reader.GetAttribute("value");
// add item to the dictionary
this.Add(key, value);
// jump to next <item>
reader.ReadStartElement("item");
reader.MoveToContent(); // workaround to trigger node type
}
// Consume the </Parameters> node as required by the documentation
// https://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.readxml%28v=vs.110%29.aspx
// Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so.
reader.Read();
}
Note there is no need to call reader.MoveToAttribute(string name)
before calling reader.GetAttribute(string name)
.