2

I've been struggling to deserialize a Dictionary with Enum Tuple keys in Json.Net. The main problem is that the deserializer can't convert the values back to the specified Enum Tuple whether it's the Enum's string or integer representation in the Json.

I've tried these solutions but none of the worked:

  1. https://stackoverflow.com/a/59310390
  2. https://gist.github.com/SteveDunn/e355b98b0dbf5a0209cb8294f7fffe24

Tried to write my own Custom Converter, the WriteJson part works, but I can't even stop the ReadJson function with breakpoints to see what happens inside.

I'd be happy to get some help with the issue. Maybe somebody already has a solution.

The JSON looks like this:

{
   "DictionaryProperty":{
      "(EnumType1Value1, EnumType2Value1)":"New",
      "(EnumType1Value1, EnumType2Value1)":"Old",
      "(EnumType1Value2, EnumType2Value1)":"Newer",
      "(EnumType1Value2, EnumType2Value2)":"Older"
}

This is the error I get:

"Could not convert string '(EnumType1Value1, EnumType2Value1)' to dictionary key type 'System.ValueTuple`2[EnumType1,EnumType2]'"

This is the custom converter I wrote:

public class CustomDictionaryConverter : JsonConverter<Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>>
{
    public override void WriteJson(JsonWriter writer, Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, ((Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>)value).ToList());
    }

    public override Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> ReadJson(
        JsonReader reader
        , Type objectType
        , Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> existingValue
        , bool hasExistingValue
        , JsonSerializer serializer)
    {
        Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> tempdict = new Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>();

        var temp = serializer.Deserialize<Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>[]>(reader)[0];

        tempdict = (Dictionary<(EnumType1 Enum1, EnumType2  Enum2), string>)temp;
        return tempdict;
    }
}
Drag and Drop
  • 2,672
  • 3
  • 25
  • 37
ucsongi
  • 31
  • 4
  • What is the json to deserialize? Is it fixed or can you work on it? If you can insert an intermediate step, I would use a simple string as a key in the dictionary, using a simple concatenation and a split to convert back and forth from the Enum Tuple. – Alberto Chiesa Jan 14 '21 at 14:21
  • Indeed, seems to be a bug in Json.net, fails on: `JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(new Dictionary<(StringComparison, StringComparison), string>{ {(StringComparison.Ordinal, StringComparison.Ordinal), "ordinal"} }))` – Charlieface Jan 14 '21 at 14:30
  • 1
    @Charlieface , The burden of [mre] is usually on the person who brings up the question. – Drag and Drop Jan 14 '21 at 14:34
  • 4
    The issue is that `(Enum,Enum)` is a **complex key** -- one not convertible to a string. Since dictionaries are serialized as JSON objects with the key becoming the property name, Json.NET doesn't know how to serialize such a dictionary, as is explained in the [docs](https://www.newtonsoft.com/json/help/html/SerializationGuide.htm#Dictionarys). – dbc Jan 14 '21 at 14:41
  • 1
    Your options are to implement a type converter for `(Enum,Enum)` as shown in [Not ableTo Serialize Dictionary with Complex key using Json.net](https://stackoverflow.com/q/24504245/3744182), or serialize the dictionary as an array as shown in [How can I serialize/deserialize a dictionary with custom keys using Json.Net?](https://stackoverflow.com/q/24681873/3744182). – dbc Jan 14 '21 at 14:41
  • 5
    *Tried to write my own Custom Converter, the WriteJson part works, but I can't even stop the ReadJson function with breakpoints to see what happens inside.* -- then please show a [mcve]. – dbc Jan 14 '21 at 14:43
  • @DragandDrop Yes, but I think this one is worth it. Should serialization not be reversible? – Charlieface Jan 14 '21 at 14:47
  • @A.Chiesa Yes I can work on it, it's not fixed, I'll give your suggestion a try, it might solve the issue. Thank you in advance. – ucsongi Jan 14 '21 at 15:05
  • @DragandDrop Thanks for the hint, I've added some code. – ucsongi Jan 14 '21 at 15:15
  • @dbc Thanks for the two suggestions, I'll try them asap. – ucsongi Jan 14 '21 at 15:22

1 Answers1

1

A bit manual (and lacking decent error checking), but works for your specified json both serializing & deserializing

public class CustomDictionaryConverter : JsonConverter<Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>>
{
    public override void WriteJson(JsonWriter writer, Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        foreach(var kv in value)
        {
            writer.WritePropertyName($"({kv.Key.Enum1.ToString()},{kv.Key.Enum2.ToString()})");
            writer.WriteValue(kv.Value);
        }
        writer.WriteEndObject();
    }

    public override Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> ReadJson(
        JsonReader reader
        , Type objectType
        , Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> existingValue
        , bool hasExistingValue
        , JsonSerializer serializer)
    {
        Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string> tempdict = new Dictionary<(EnumType1 Enum1, EnumType2 Enum2), string>();
        JObject jObject = JObject.Load(reader);
        foreach(var kv in ((IEnumerable<KeyValuePair<string, JToken>>)jObject)) 
        {
            var keys = kv.Key.Replace("(","").Replace(")","").Split(",");
            var key1 = Enum.Parse<EnumType1>(keys[0]);
            var key2 = Enum.Parse<EnumType2>(keys[1]);
            var value = kv.Value.ToString();
            tempdict.Add((key1,key2),value);
        }
        return tempdict;
    }
}

Live example: https://dotnetfiddle.net/w85HgK

Jamiec
  • 133,658
  • 13
  • 134
  • 193