【问题标题】:newtonsoft json.net - deserializing dictionary with value tuple keynewtonsoft json.net - 使用值元组键反序列化字典
【发布时间】:2019-12-10 12:28:29
【问题描述】:

使用值元组键反序列化字典时出现错误。我认为它将元组转换为字符串,然后无法将其反序列化为键:

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

【问题讨论】:

标签: c# dictionary json.net


【解决方案1】:

就像您在 OP 中已经提到的那样,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 转换器:Unable to cast object of type 'ConsoleApp.TupleConverter`2[System.Int32,System.Int32]' to type 'Newtonsoft.Json.JsonConverter'
  • 您不需要在 JsonConverterAttribute 中传递上述内容。 TypeDescriptor.AddAttributes 将为 (String,string) 添加 TypeConverter。
  • 如果您使用预期的格式,这会在您每次反序列化时为第二个字符串添加一个空格。我在将它与其他类型如 Ulong 一起使用时也遇到了问题。经过一些修复后,它工作正常,谢谢。
  • @Fox 我已经编辑了答案以正确处理逗号后的空格,如果它们不是string,也可以正确转换为 T1 和 T2。
【解决方案2】:

从您的问题中我可以看出,您的 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);

【讨论】:

  • 就我而言,我使用的是值元组:public Dictionary&lt;(int Id, int AnotherId), int&gt; Types { get; set; }
猜你喜欢
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 1970-01-01
  • 2015-02-16
  • 2013-03-23
  • 1970-01-01
  • 2021-11-10
相关资源
最近更新 更多