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);
5

2 Answers 2

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);
Sign up to request clarification or add additional context in comments.

4 Comments

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'
you needn't pass the above in a JsonConverterAttribute. The TypeDescriptor.AddAttributes would add the TypeConverter for (String,string).
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 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.
-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);

1 Comment

In my case, I am using value tuple: public Dictionary<(int Id, int AnotherId), int> Types { get; set; }

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.