Dictionary conversion for complex key-types is really buggy, defaults to ToString() output
See original GitHub issueSource/destination types
using ActionRewards = System.Tuple<float, float>;
using QTable = System.Collections.Generic.Dictionary<Vector2, System.Tuple<float, float>>;
[JsonObject]
public struct Vector2
{
public Vector2(float x, float y) { this.x = x; this.y = y; }
[JsonProperty]
public float x;
[JsonProperty]
public float y;
public override string ToString()
{
return $"({ x }, { y })";
}
}
Source/destination JSON
// expected output
{{"x":0.7,"y":0.3}:{"Item1":0.5,"Item2":0.6}}
// actual output
{"(0.7, 0.3)":{"Item1":0.5,"Item2":0.6}}
Expected behavior
I expected the dictionary’s key to be serialized using Newtonsoft.Json class annotations or just the standard behavior of the serializer for a struct object (which even works fine without annotations). Btw, converting the struct to a class didn’t help either and caused to same behavior.
Actual behavior
From the output of my little test program you can see that the dictionary’s key was serialized using the output of ToString().
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Could not convert string ‘(0.7, 0.3)’ to dictionary key type ‘Vector2’. Create a TypeConverter to convert from the string to the key type object. Path ‘[’(0.7, 0.3)‘]’, line 1, position 14. —> Newtonsoft.Json.JsonSerializationException: Error converting value “(0.7, 0.3)” to type ‘Vector2’. Path ‘[’(0.7, 0.3)‘]’, line 1, position 14. —> System.ArgumentException: Could not cast or convert from System.String to Vector2. at Newtonsoft.Json.Utilities.ConvertUtils.EnsureTypeAssignable(Object value, Type initialType, Type targetType) at Newtonsoft.Json.Utilities.ConvertUtils.ConvertOrCast(Object initialValue, CultureInfo culture, Type targetType) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType) — End of inner exception stack trace — at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id) — End of inner exception stack trace — 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.CreateValueInternal(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.JsonSerializer.Deserialize(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 Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value) at dotnet_test.Program.Main(String[] args) in /home/rl-dev2/dotnet-test/Program.cs:line 46
Steps to reproduce
public class Program
{
public static void Main(string[] args)
{
string vectorContent = "{ \"x\": 0.7, \"y\": 0.3 }";
// test single Vector2 serialization
var vector = JsonConvert.DeserializeObject<Vector2>(vectorContent);
Console.WriteLine($"{ vector }");
string serVectorContent = JsonConvert.SerializeObject(vector);
Console.WriteLine($"serialized json: {serVectorContent }");
var reserializedVector = JsonConvert.DeserializeObject<Vector2>(serVectorContent);
Console.WriteLine($"{ reserializedVector }");
// test Vector2 serialization as dictionary key
var dict = new QTable() {
{ new Vector2(0.7f, 0.3f), new ActionRewards(0.5f, 0.6f) }
};
string dictJson = JsonConvert.SerializeObject(dict); // serializes the key using ToString() function output
Console.WriteLine($"{ dictJson }");
var serDict = JsonConvert.DeserializeObject<QTable>(dictJson); // throws a conversion error
Console.WriteLine($"{ serDict }");
}
}
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (2 by maintainers)
I did resolve my issue with a workaround converting the 2D vector into a single number by multiplying it with the screen size. If I find the time I could also try your suggested solution, but it’s only a small student project, so I’m not sure if the effort is worth it. But anyways … Thanks a lot for your help, Tyler 😃
JsonConverter isn’t used for dictionary keys because they’re strings, not JSON. That is why ToString or a TypeConverter is used.