Serialize Dictionary<TKey, TValue> to JSON with DataContractJsonSerializer
Asked Answered
B

3

15

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

Brut answered 21/9, 2011 at 10:23 Comment(4)
It seems like you could wrap the dictionary with an IEnumerable class and your ISerializable implentation. Much less work than trying to inherit from a dictionary.Dipterous
You could also just use the JavaScriptSerializer as it serializes dictionaries how you prefer.Gurkha
@RitchMelton - inheriting from dictionary is not a lot of work, but I must admit that implementing IEnumerable and returning the member dictionary's getEnumarator at first sight appears to be better than my workaround - only one method to implementBrut
@PaulTyng - as mentioned in the update, changing the serializer will have a wider impact and it's not worth it just for this minor design improvement. Here I'm driven more by my curiousity why the trick with DataCotntractJsonSerializer is not workingBrut
I
4

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);
        }
    }
}
Ifc answered 17/9, 2013 at 21:32 Comment(2)
If you control the classes this works very well, and you can make the CustomDictionarySerializer Value type a generic parameter so you can serialize (and deserialize) any type of dictionary. Note that any value of reference type also adds a "__type" property (for deserialization purposes). Also you must use the DataContract attribute on the class.Dexterdexterity
This doesn't work (or at least, not easily) for types like Dict<string, List<object>>Dhiren
W
1

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.

Whiffletree answered 10/10, 2011 at 13:37 Comment(2)
Thanks for the answer. The linked article by Dan Rigsby just mentions that DataContractSerializer is not configurable but doesn't go anywhere near the lengths I've gone in exploring why explicit GetObjectData implementation is not being called. Other than that, I know json.net well, and I know it's good - just changing the serialization engine in the project will have too big an impact - it's not just my custom Dictionary that's being serializedBrut
Rigsby link is now dead (DNS failure). All the standard serialization customization mechanisms are available, because this is all based on XmlObjectSerializer. Unfortunately, this is not "simple" to tap into (see "surrogate property" answer), but far from "no way".Dexterdexterity
C
0

As @Pashec mentioned, if you use Json.NET, you can try the following:

public Message <methodname> {
    ...
    string jsonMessage = JsonConvert.SerializeObject(myObjectTree);
    return WebOperationContext.Current.CreateTextResponse(jsonMessage, "application/javascript; charset=utf-8", Encoding.UTF8);
}
Cineraria answered 24/2, 2012 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.