1

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>
dbc
  • 104,963
  • 20
  • 228
  • 340
Martin
  • 397
  • 3
  • 16
  • Have you tried some of the other dictionary deserialization methods on SO? http://stackoverflow.com/questions/12554186/how-to-serialize-deserialize-to-dictionaryint-string-from-custom-xml-not-us – thinklarge Dec 21 '15 at 21:33
  • Is your XML formatted correctly? It doesn't have an end tag for the MaintenanceIndicators collection element. – Miles Grimes Dec 21 '15 at 21:34
  • I have updated the XML, it is correct in the source, I just pasted it wrong. – Martin Dec 22 '15 at 07:07
  • @thinklarge I started with that example and I use that for the more complex dictionaries, however I just rewrote it to use attributes instead of elements. As at another part in the app I serialize it similar to a string in entity framework, using the attributes it is a lot smaller than using elements. – Martin Dec 22 '15 at 07:08

2 Answers2

2

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).

Community
  • 1
  • 1
dbc
  • 104,963
  • 20
  • 228
  • 340
0

If you need to work with low typed object (as dictionary could appear) use javascript serializer instead of XML is much more flexible.

Eg :

    private void button1_Click(object sender, EventArgs e)
    {
        List<Dictionary<string, string>> datas = new List<Dictionary<string, string>>();
        Dictionary<string,string> d1 = new Dictionary<string,string> ();
        d1.Add ( "key11","value11");
        d1.Add ( "key12","value12");
        Dictionary<string,string> d2 = new Dictionary<string,string> ();
        d2.Add ( "key21","value21");
        d2.Add ( "key22","value22");
        datas.Add(d1);
        datas.Add(d2);
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        string serialized = serializer.Serialize (datas);

        List<Dictionary<string, string>> deserialized = serializer.Deserialize<List<Dictionary<string, string>>>(serialized);
    }

Let me know if that kind of serializatin is acceptable in your scenario

Skary
  • 1,322
  • 1
  • 13
  • 40
  • Actually I have a lot of more XML around it that is also serialized / deserialized, this is only a small part of it. – Martin Dec 22 '15 at 07:06
  • if you have not to guarant backward compatibility, with JavaScriptSerializer you can serialize-deserialize at least all you serialzie-deserialize at the moment. In addition you could serialize deserialize dictionary, dynamic and other "low typed" object natively (so you can avoid handle serialization manually with all the problem relate). – Skary Dec 22 '15 at 07:28