Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize Dictionary<TKey, TValue> to JSON with DataContractJsonSerializer

I have an object tree that I'm serializing to JSON with DataContractJsonSerializer. Dictionary<TKey, TValue> gets serialized but I don't like the markup - the items are not rendered like this:

{key1:value, key2:value2}

but rather like an array of serialized KeyValuePair<TKey, TValue> objects:

[{
    "__type":"KeyValuePairOfstringanyType:#System.Collections.Generic",
    "key":"key1",
    "value":"value1"
 },     
 {
    "__type":"KeyValuePairOfstringanyType:#System.Collections.Generic",
    "key":"key2",
    "value":"value2"
 }]

Ugly, isn't it?

So, I avoid this by wrapping the generic Dictionary in a custom object that implements ISerializable, and I implement my custom serialization in the GetObjectData method (and it takes just 3 lines).

Now the problem - I can't make my class derive from Dictionary<TKey, TValue>, so I implement all logic (Add, Clear, etc.) in my custom class, being applied to a private Dictionary<TKey, TValue> field. Inheritance would be preferable as I'll have all generic Dictionary functionality at my disposal when using my custom object.

The problem with inheritance is that Dictionary<TKey, TValue> implements ISerializable on its own, and DataContractJsonSerializer seems to prefer this implementation even if I implement ISerializable explicitly from my custom class, like this:

public class MyClass : Dictionary<string, object>, ISerializable
{
    public override void GetObjectData(SerializationInfo info, 
        StreamingContext context)
}

I was actually surprised that this is possible as it allows me to implement the same interface twice without being obviously able to use explicit interface implementation - so I analyzed the situation in more detail in a blog post about multiple interface implementation

So, according to the experiments I did there, the serializer should be calling my implementation of ISerializable, no matter what type of casting is used internally -

((ISerializable)((Dictionary<,>)obj)).GetObjectData(...)

or:

((ISerializable)obj).GetObjectData(...)

but it apparently isn't happening as I see in the resulting JSON that the KeyValuePair<TKey, TValue> serializer still being called. What might be happening that I'm missing?

Update: The answers and comments I'm getting so far are pretty much only suggesting workarounds. I noted, however, that I have a workaround that works quite well so with asking this question I have 2 objectives:

  1. Eventually make it work with the original design - and I'm not going to change serialization logic just for that, there is a lot of code and logic dependent on it

  2. To unserstand the mystery of why isn't the DataContractJsonSerializer using my serialization code - as can be seen in the blog post I referred, I have made all kinds of experiments with interface implementation and inheritance and I was confident that I'm grasping all the ins and outs of the process, so I'm troubled by failing to understand what's hapenning in this case

like image 482
Vroomfundel Avatar asked Sep 21 '11 10:09

Vroomfundel


2 Answers

One option is using a surrogate property and have the dictionary be inside the custom ISerializable type, that way you don't need to worry about inheritance:

public Dictionary<string, string> NodeData { get; set; }

[DataMember(Name="NodeData")]
private CustomDictionarySerializer NodeDataSurrogate
{
    get
    {
        return new CustomDictionarySerializer(NodeData);
    }
    set
    {
        NodeData = value._data;
    }
}

[Serializable]
private class CustomDictionarySerializer : ISerializable
{
    public Dictionary<string, string> _data;

    public CustomDictionarySerializer(Dictionary<string, string> dict)
    {
        _data = dict;
    }

    public CustomDictionarySerializer(SerializationInfo info, StreamingContext context)
    {
        _data = new Dictionary<string, string>();
        var valueEnum = info.GetEnumerator();
        while(valueEnum.MoveNext())
        {
            _data[valueEnum.Current.Name] = valueEnum.Current.Value.ToString();
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        foreach (var pair in _data)
        {
            info.AddValue(pair.Key, pair.Value);
        }
    }
}
like image 119
BrandonAGr Avatar answered Oct 05 '22 01:10

BrandonAGr


Seems, there is no way to customize DataContractJsonSerializer.

If you still want to achieve what you want, consider using Json.Net. It's faster and more flexible than DataContractJsonSerializer. Look at JsonConverter conception of Json.Net. It gives you the possibility to customize serialization/deserialization process.

Moreover default implementation of dictionary serialization is exact as you want http://james.newtonking.com/projects/json/help/SerializingCollections.html.

like image 43
Pashec Avatar answered Oct 04 '22 23:10

Pashec