How to Serialize/Deserialize to 'Dictionary<Int, String>' from Custom Xml Not Using Xelement

How to serialize/deserialize to `Dictionary int, string ` from custom XML not using XElement?

With the help of a temporary item class

public class item
{
[XmlAttribute]
public int id;
[XmlAttribute]
public string value;
}

Sample Dictionary:

Dictionary<int, string> dict = new Dictionary<int, string>()
{
{1,"one"}, {2,"two"}
};

.

XmlSerializer serializer = new XmlSerializer(typeof(item[]), 
new XmlRootAttribute() { ElementName = "items" });

Serialization

serializer.Serialize(stream, 
dict.Select(kv=>new item(){id = kv.Key,value=kv.Value}).ToArray() );

Deserialization

var orgDict = ((item[])serializer.Deserialize(stream))
.ToDictionary(i => i.id, i => i.value);

------------------------------------------------------------------------------

Here is how it can be done using XElement, if you change your mind.

Serialization

XElement xElem = new XElement(
"items",
dict.Select(x => new XElement("item",new XAttribute("id", x.Key),new XAttribute("value", x.Value)))
);
var xml = xElem.ToString(); //xElem.Save(...);

Deserialization

XElement xElem2 = XElement.Parse(xml); //XElement.Load(...)
var newDict = xElem2.Descendants("item")
.ToDictionary(x => (int)x.Attribute("id"), x => (string)x.Attribute("value"));

Cannot serialize member ... of type System.Collections.Generic.Dictionary`2 because it implements IDictionary

Your issue is as follows:

  1. Dictionary<TKey, TValue> is supported by the data contract serializer used in WCF, as explained in the docs. This is why your DetailLog class can be sent over the wire successfully by WCF as a root object.

  2. This serializer also supports IXmlSerializable to allow types to manually serialize themselves to XML.

  3. DataTable implements IXmlSerializable.

  4. Internally, DataTable serializes non-primitive entries using XmlSerializer -- a completely different serializer that uses a completely different code base.

  5. XmlSerializer does not support dictionaries. Thus your DetailLog cannot be sent over the wire by WCF when nested in a DataTable.

  6. As an unrelated problem, you also need to set the data table name, serialization will throw an exception if you do not:

        data.TableName = "CustomDetailReport"; // For instance

To work around the issue with dictionaries, you need to make your DetailLog class serializable by both serializers. The question How to serialize/deserialize to Dictionary<int, string> from custom XML not using XElement? gives a variety of ways to serialize its dictionary property, including using a proxy array property:

[XmlType("KeyValue"), XmlRoot("KeyValue")]
public class SerializableKeyValuePair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
}

public static class SerializableKeyValuePairExtensions
{
public static SerializableKeyValuePair<TKey, TValue> ToSerializablePair<TKey, TValue>(this KeyValuePair<TKey, TValue> pair)
{
return new SerializableKeyValuePair<TKey, TValue> { Key = pair.Key, Value = pair.Value };
}
}

[DataContract]
public class DetailLog
{
[DataMember]
public string Name { get; set; }

[DataMember]
public string SubAction { get; set; }

[DataMember]
[XmlIgnore]
public Dictionary<string, string> Fields { get; set; }

[IgnoreDataMember]
[XmlArray("Fields")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public SerializableKeyValuePair<string, string>[] FieldsArray
{
get
{
return Fields == null ? null : Fields.Select(p => p.ToSerializablePair()).ToArray();
}
set
{
Fields = value == null ? null : value.ToDictionary(p => p.Key, p => p.Value);
}
}

[DataMember]
public string UserName { get; set; }
}

WCF should now be able to send your DataTable over the wire successfully.

How to Deserialize a custom object having dictionary as a member?

You can solve this with Newtonsoft.Json library. To serialize start with converting your object to json, then use DeserializeXNode method:

var student = new Student()
{
FirstName = "FirstName",
LastName = "LastName",
Books = new Dictionary<string, string>
{
{ "abc", "42" },
}
};

var json = JsonConvert.SerializeObject(student);
var xml = JsonConvert.DeserializeXNode(json, "Root");
var result = xml.ToString(SaveOptions.None);
// result is "<Root><books><abc>42</abc></books><Books><abc>42</abc></Books><FirstName>FirstName</FirstName><LastName>LastName</LastName></Root>"

To deserialize you can use SerializeXmlNode method:

var input = "<Root><books><abc>42</abc></books><Books><abc>42</abc></Books><FirstName>FirstName</FirstName><LastName>LastName</LastName></Root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(input);
var json = JsonConvert.SerializeXmlNode(doc);
var template = new {Root = (Student) null};
var result = JsonConvert.DeserializeAnonymousType(json, template);
// result.Root is an instance of Student class

How to XML deserialize Dictionary, that hidden in other element?

Your data-structure doesn´t really fit your xml well. If you have a simple data-type such as int or string you can serialize and de-serialize directly into an xml-node. If you have some more complex data-structure like your Firnedship-node you need a nested node.

Having said this your data-structure should be similar to thie following:

public class SaveGame
{
public Player player { get; set; }
}

public class Player
{
public item[] friendshipData { get; set; }
}

public class item
{
public Key key { get; set; }
public Friendship value { get; set; }
}

// add this class with a single string-field
public class Key
{
public string @string { get; set; }
}

public class Friendship
{
public int Points { get; set; }
}

As an aside consider following naming-conventions, which is giving classes and members of those classes PascaleCase-names, e.g. FriendshipData, Item, and Key. This however assumes you have some mapping from those names to your names within the xml. This can be done by using XmlElementAttribute:

public class Player
{
[XmlElement("friendshipData ")] // see here how to change the name within the xml
public item[] FriendshipData { get; set; }
}

Dictionary string, string serialization: after ReadXml(), subsequent XML elements are skipped

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

Serialize Class containing Dictionary member

You can't serialize a class that implements IDictionary. Check out this link.

Q: Why can't I serialize hashtables?

A: The XmlSerializer cannot process
classes implementing the IDictionary
interface. This was partly due to
schedule constraints and partly due to
the fact that a hashtable does not
have a counterpart in the XSD type
system. The only solution is to
implement a custom hashtable that does
not implement the IDictionary
interface.

So I think you need to create your own version of the Dictionary for this. Check this other question.

Map a Dictionary in Entity Framework Code First Approach

Entity Framework does not presently support mapping a Dictionary natively.

See the following for more information and work-arounds:

Entity Framework 4 POCO with Dictionary

EF Code First - Map Dictionary or custom type as an nvarchar

http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/a51ba903-2b8b-448e-8677-d140a0b43e89/



Related Topics



Leave a reply



Submit