4

Is there a way to use Json.NET for (de)serialization but continue to use the dictionary serialization conventions of DataContractJsonSerializer?

In other words, is there a way to read and write JSON in this structure:

{ "MyDict" : [
    { "Key" : "One",
      "Value" : 1 },
    { "Key" : "Two",
      "Value" : 2 }
    ] }

Using a class like this (with Json.NET attributes):

public class MyClass
{
    public Dictionary<string, int> MyDict { get; set; }
}

It seems like Json.NET's KeyValuePairConverter might help somehow, but if so, I can't find the correct way to apply it.

I tried attributing MyDict with [JsonProperty(ItemConverterType = typeof(KeyValuePairConverter))], this page even seems to imply that could work, but adding such an attribute results in an ArgumentOutOfRangeException during serialization.

Jon-Eric
  • 16,977
  • 9
  • 65
  • 97

1 Answers1

3

You're right, the KeyValuePairConverter doesn't seem to work correctly here. Without digging into the Json.Net source code, I can only speculate as to why. As a workaround, you can make your own custom JsonConverter to do this translation quite easily:

class MyDictionaryConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Dictionary<string, T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JArray array = JArray.Load(reader);
        Dictionary<string, T> dict = new Dictionary<string, T>();
        foreach (JObject obj in array.Children<JObject>())
        {
            string key = obj["Key"].ToString();
            T val = obj["Value"].ToObject<T>();
            dict.Add(key, val);
        }
        return dict;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Dictionary<string, T> dict = (Dictionary<string, T>)value;
        JArray array = new JArray();
        foreach (KeyValuePair<string, T> kvp in dict)
        {
            JObject obj = new JObject();
            obj.Add("Key", kvp.Key);
            obj.Add("Value", JToken.FromObject(kvp.Value));
            array.Add(obj);
        }
        array.WriteTo(writer);
    }
}

Apply the converter to the dictionary using a [JsonConverter] attribute:

public class MyClass
{
    [JsonConverter(typeof(MyDictionaryConverter<int>))]
    public Dictionary<string, int> MyDict { get; set; }
}

Here is a short demo program showing the converter in action (full round trip):

class Program
{
    static void Main(string[] args)
    {
        MyClass mc = new MyClass { MyDict = new Dictionary<string, int>() };
        mc.MyDict.Add("One", 1);
        mc.MyDict.Add("Two", 2);

        string json = JsonConvert.SerializeObject(mc, Formatting.Indented);
        Console.WriteLine(json);
        Console.WriteLine();

        MyClass mc2 = JsonConvert.DeserializeObject<MyClass>(json);
        foreach (KeyValuePair<string, int> kvp in mc2.MyDict)
        {
            Console.WriteLine(kvp.Key + " == " + kvp.Value);
        }
    }
}

Output of the above:

{
  "MyDict": [
    {
      "Key": "One",
      "Value": 1
    },
    {
      "Key": "Two",
      "Value": 2
    }
  ]
}

One == 1
Two == 2
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • What if the Dictionary is what you're trying to serialize/deserialize, rather than it being embedded within MyClass? – Justin Jan 07 '16 at 20:09
  • @Justin - You can pass an instance of the converter to `(De)SerializeObject` instead of using an attribute. However, see [Json.Net - Serialize Dictionary as Array (of Key Value Pairs)](http://stackoverflow.com/q/12751354/10263) for other solutions. Using a contract resolver is probably a better solution. – Brian Rogers Jan 07 '16 at 21:00
  • In my case, the json has an object {} of "unnamed" key value pairs, and not []. Can you suggest what to do in that case? – shanti Oct 17 '17 at 16:57
  • @shanti I suggest you [ask a new question](https://stackoverflow.com/questions/ask). I don't know what you mean by "unnamed key value pairs". You would need to lay that out in a question and describe what you are trying to do that is different from this question. Comments are not a good place for that because they are limited in length and formatting. When you create the new question, you can provide [a link back to this one](https://stackoverflow.com/q/24042870/10263) to provide context. – Brian Rogers Oct 17 '17 at 17:32
  • ok I will do that. Thanks for letting me know. – shanti Oct 17 '17 at 18:32