Newtonsoft Json.Net - 反序列化带值元组键的字典

3

当我反序列化一个包含元组值键的字典时,出现了错误。我认为它会将元组转换为字符串,然后无法将其反序列化回作为键:

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].

有没有标准的解决方案?

目前看起来,我需要提供一个自定义转换器,这看起来很繁琐。

更新:

这是我正在尝试序列化/反序列化的类:

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>();
}

这就是我使用它的地方:

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

2
一段定义原始结构的代码加上 JSON 字符串将非常有帮助。 - Pavel Kovalev
@TobiasTengler 为什么你不建议使用自定义转换器? - user47589
有标准解决方案吗?是的。您可以像如何使用Json.Net序列化/反序列化具有自定义键的字典?中所示那样将其序列化为数组,或者添加自定义TypeConverter以将元组转换为字符串,如无法使用Json.net序列化具有复杂键的字典中所示。 - dbc
请注意Bug C#: Dictionary with tuple key #2022这不是一个错误。当字典的字符串键时,不是所有类型都可以往返序列化。 - dbc
可能是在C#中反序列化带有元组键的字符串的重复问题。 - pcdev
2个回答

4

正如您在原始帖子中提到的那样,TypeConverter在此处将有助于反序列化元组键。但它可能没有看起来那么繁琐。

例如,您可以编写一个简单的TypeConverter,如下所示。

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

现在,您可以执行以下操作。

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

实际上,在这种情况下,我需要一个 JsonConverter 而不是一个 TypeConverter。我试图在 JsonConverterAttribute 中传递上述内容,但它给了我一个错误,指出转换器不是 json 转换器:无法将类型为 'ConsoleApp.TupleConverter`2[System.Int32,System.Int32]' 的对象强制转换为类型 'Newtonsoft.Json.JsonConverter' - herme 0
你不需要在JsonConverterAttribute中传递上述内容。TypeDescriptor.AddAttributes将为(String,string)添加TypeConverter。 - Anu Viswan
1
如果您使用了预期的格式,每次反序列化第二个字符串时都会添加一个空格。我还在使用其他类型(如Ulong)时遇到了问题。不过经过一些修复,现在它可以正常工作了,谢谢。 - Fox
@Fox 我已经编辑了答案,以正确处理逗号后的空格,并在T1和T2不是“string”时正确转换。 - 0xced

-1
从你的问题中我可以看出,在你的json对象中,你以某种方式得到了一个(1,2),这不是newtonsoft序列化元组的方式,因此将无法进行反序列化而不使用自定义反序列化器。Newtonsoft将元组序列化为{"Item1":1,"Item2":2}。这就是为什么你的代码不起作用。如果你不能改变输入,你必须编写一个自定义的反序列化器,但我建议将输入更改为标准格式。这里的代码是如何序列化/反序列化元组的:
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
在我的情况下,我正在使用值元组:public Dictionary<(int Id, int AnotherId), int> Types { get; set; } - herme 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接