3

I get an error when deserializing a dictionary with value tuple keys. I think it converts the tuple into a string then is unable to deserialize it back as a key:

Newtonsoft.Json.JsonSerializationException
  HResult=0x80131500
  Message=Could not convert string '(1, 2)' to dictionary key type 'System.ValueTuple`2[System.Int32,System.Int32]'. Create a TypeConverter to convert from the string to the key type object. Path 'Types['(1, 2)']', line 1, position 49.
  Source=Newtonsoft.Json
  StackTrace:
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at ConsoleApp.Program.Main(String[] args) in D:\Open Source\JsonSerilization\ConsoleApp\ConsoleApp\Program.cs:line 65

Inner Exception 1:
JsonSerializationException: Error converting value "(1, 2)" to type 'System.ValueTuple`2[System.Int32,System.Int32]'. Path 'Types['(1, 2)']', line 1, position 49.

Inner Exception 2:
ArgumentException: Could not cast or convert from System.String to System.ValueTuple`2[System.Int32,System.Int32].

Is there a standard solution for this?

So far, it seems like I would need to provide a custom converter; which looks tedious.

Update:

Here is the class I am trying to serialize/deserialize:

public sealed class Message
{
    [JsonConstructor]
    internal Message()
    { }

    public ISet<(int Id, int AnotherId)> Ids { get; set; }
        = new HashSet<(int Id, int AnotherId)>();

    public Dictionary<(int Id, int AnotherId), int> Types { get; set; }
        = new Dictionary<(int Id, int AnotherId), int>();
}

And here is where I use it:

var message = new Message();
message.Ids.Add((1, 2));
message.Types[(1, 2)] = 3;

var messageStr = JsonConvert.SerializeObject(message);
var messageObj = JsonConvert.DeserializeObject<Message>(messageStr);
herme 0
  • 912
  • 1
  • 9
  • 23
  • 2
    A piece of code which defines original structure + json string would be really helpful here – Pavel Kovalev Aug 01 '19 at 21:07
  • @TobiasTengler Why do you recommend against the custom converter? –  Aug 01 '19 at 21:18
  • *Is there a standard solution?* -- yes. You can serialize 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), or add a custom `TypeConverter` to convert the tuple to a string as shown in [Not ableTo Serialize Dictionary with Complex key using Json.net](https://stackoverflow.com/a/31412665/3744182). – dbc Aug 01 '19 at 21:50
  • Note also [Bug C#: Dictionary with tuple key #2022](https://github.com/JamesNK/Newtonsoft.Json/issues/2022#issuecomment-478211562): *That's not a bug. Not all types roundtrip serialize when the string key of a dictionary.*. – dbc Aug 01 '19 at 21:52
  • Possible duplicate of [Deserialize string with Tuple key in C#](https://stackoverflow.com/questions/48715635/deserialize-string-with-tuple-key-in-c-sharp) – pcdev Oct 03 '19 at 07:09

2 Answers2

4

Like you have already mentioned in your OP, TypeConverter would be useful here to Deserialize the Tuple Key. But it might not be as tedious as it might seems to be.

For example, You could write a simple TypeConverter as shown below.

public class TupleConverter<T1, T2>: TypeConverter 
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context,CultureInfo culture, object value)
    {
        var key = Convert.ToString(value).Trim('(').Trim(')');
        var parts = Regex.Split(key, (", "));
        var item1 = (T1)TypeDescriptor.GetConverter(typeof(T1)).ConvertFromInvariantString(parts[0])!;
        var item2 = (T2)TypeDescriptor.GetConverter(typeof(T2)).ConvertFromInvariantString(parts[1])!;
        return new ValueTuple<T1, T2>(item1, item2);
    }
}

Now, you can do the following.

var dictionary = new Dictionary<(string,string),int>
{
   [("firstName1","lastName1")] = 5,
   [("firstName2","lastName2")] = 5
};

TypeDescriptor.AddAttributes(typeof((string, string)),  new TypeConverterAttribute(typeof(TupleConverter<string, string>)));
var json = JsonConvert.SerializeObject(dictionary);
var result = JsonConvert.DeserializeObject<Dictionary<(string,string),string>>(json);
0xced
  • 25,219
  • 10
  • 103
  • 255
Anu Viswan
  • 17,797
  • 2
  • 22
  • 51
  • Actually, in this case I would need a `JsonConverter` rather than a `TypeConverter`. I tried to pass the above in a `JsonConverterAttribute` and it gave me an error stating that the converter is not a json converter: ```Unable to cast object of type 'ConsoleApp.TupleConverter`2[System.Int32,System.Int32]' to type 'Newtonsoft.Json.JsonConverter'``` – herme 0 Aug 02 '19 at 12:05
  • you needn't pass the above in a JsonConverterAttribute. The TypeDescriptor.AddAttributes would add the TypeConverter for (String,string). – Anu Viswan Aug 04 '19 at 02:26
  • 1
    If you use intended formatting, this adds a whitespace to the second string everytime you deserialize. I also had problems with using this with other types as Ulong. After some fixing it works fine though, thx. – Fox May 28 '20 at 09:15
  • @Fox I have edited the answer to properly handle the whitespace after the comma, and also to properly convert to T1 and T2 if they are not `string`. – 0xced Dec 09 '20 at 19:55
-1

What I can tell from your question is that you somehow got a (1, 2) in your json object, which is not how newtonsoft serializes tuples and therefore will not be able to serialize it back without a custom deserilizer. Newtonsoft serializes Tuples as {"Item1" : 1, "Item2": 2}. This is why your code does not work. If you can not change the input, you have to write a custom deserializer, but I would recommend changing the input to a standard. The code here is how you serialize/deserialize a tuple:

var dictionary = new Dictionary<string, Tuple<int, int>>();
dictionary.Add("test", new Tuple<int, int>(1, 2));
var serializeObject = JsonConvert.SerializeObject(dictionary);
var deserializeObject = JsonConvert.DeserializeObject<Dictionary<string, Tuple<int, int>>>(serializeObject);

Assert.AreEqual(deserializeObject["test"].Item1, 1);
modmoto
  • 2,901
  • 7
  • 29
  • 54
  • 1
    In my case, I am using value tuple: `public Dictionary<(int Id, int AnotherId), int> Types { get; set; }` – herme 0 Aug 02 '19 at 11:46