如何使用 Json.Net 序列化/反序列化带有自定义键的字典?

Posted

技术标签:

【中文标题】如何使用 Json.Net 序列化/反序列化带有自定义键的字典?【英文标题】:How can I serialize/deserialize a dictionary with custom keys using Json.Net? 【发布时间】:2014-09-01 03:26:00 【问题描述】:

我有以下类,用作字典中的键:

    public class MyClass
    
        private readonly string _property;

        public MyClass(string property)
        
            _property = property;
        

        public string Property
        
            get  return _property; 
        

        public override bool Equals(object obj)
        
            MyClass other = obj as MyClass;
            if (other == null) return false;
            return _property == other._property;
        

        public override int GetHashCode()
        
            return _property.GetHashCode();
        
    

我正在运行的测试在这里:

    [Test]
    public void SerializeDictionaryWithCustomKeys()
    
        IDictionary<MyClass, object> expected = new Dictionary<MyClass, object>();
        expected.Add(new MyClass("sth"), 5.2);
        JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings  TypeNameHandling = TypeNameHandling.All ;
        string output = JsonConvert.SerializeObject(expected, Formatting.Indented, jsonSerializerSettings);
        var actual = JsonConvert.DeserializeObject<IDictionary<MyClass, object>>(output, jsonSerializerSettings);
        CollectionAssert.AreEqual(expected, actual);
    

测试失败,因为 Json.Net 似乎在字典键上使用了ToString() 方法,而不是正确地序列化它们。上面测试的结果 json 是:


  "$type": "System.Collections.Generic.Dictionary`2[[RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass, RiskAnalytics.UnitTests],[System.Object, mscorlib]], mscorlib",
  "RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass": 5.2

这显然是错误的。我怎样才能让它工作?

【问题讨论】:

JSON 并不总是能很好地处理字典,为什么不直接覆盖 MyClass.ToString() 为什么字典键这么复杂?为什么在你的类中没有字典值,然后用列表替换字典? 你能指定预期的输出应该是什么样子吗?因为我很确定复杂的属性名称不是 JSON 的一部分... @TMcKeown,在实际代码中有不同类型的键。我可以在所有这些上实现ToString() 和相应的类型转换器,但我希望序列化程序能为我完成这项工作...... @mason,显然不是。 【参考方案1】:

这应该可以解决问题:

序列化:

JsonConvert.SerializeObject(expected.ToArray(), Formatting.Indented, jsonSerializerSettings);

通过调用expected.ToArray(),您正在序列化KeyValuePair&lt;MyClass, object&gt; 对象数组而不是字典。

反序列化:

JsonConvert.DeserializeObject<KeyValuePair<IDataKey, object>[]>(output, jsonSerializerSettings).ToDictionary(kv => kv.Key, kv => kv.Value);

在这里你反序列化数组,然后使用.ToDictionary(...) 调用检索字典。

我不确定输出是否符合您的期望,但肯定它通过了相等性断言。

【讨论】:

【参考方案2】:

Grx70 的回答很好 - 只是在这里添加一个替代解决方案。我在一个 Web API 项目中遇到了这个问题,我没有调用 SerializeObject,而是允许序列化自动发生。

这个基于Brian Rogers' answer to a similar question 的自定义JsonConverter 对我有用:

public class DeepDictionaryConverter : JsonConverter

    public override bool CanConvert(Type objectType)
    
        return (typeof(IDictionary).IsAssignableFrom(objectType) ||
                TypeImplementsGenericInterface(objectType, typeof(IDictionary<,>)));
    

    private static bool TypeImplementsGenericInterface(Type concreteType, Type interfaceType)
    
        return concreteType.GetInterfaces()
               .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        Type type = value.GetType();
        IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
        IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
        IEnumerator valueEnumerator = values.GetEnumerator();

        writer.WriteStartArray();
        foreach (object key in keys)
        
            valueEnumerator.MoveNext();

            writer.WriteStartArray();
            serializer.Serialize(writer, key);
            serializer.Serialize(writer, valueEnumerator.Current);
            writer.WriteEndArray();
        
        writer.WriteEndArray();
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

在我的例子中,我在一个类上序列化一个Dictionary&lt;MyCustomType, int&gt; 属性,其中MyCustomType 具有NameId 之类的属性。结果如下:

...
"dictionaryProp": [
    [
      
        "name": "MyCustomTypeInstance1.Name",
        "description": null,
        "id": null
      ,
      3
    ],
    [
      
        "name": "MyCustomTypeInstance2.Name",
        "description": null,
        "id": null
      ,
      2
    ]
]
...

【讨论】:

这个解决方案非常棒。您是否也可以通过提供 ReadJson 方法的实现来完成此操作?谢谢! 但是如何反序列化呢? 凹凸。我也想看看反序列化。 这是一个反序列化的要点:gist.github.com/SteveDunn/e355b98b0dbf5a0209cb8294f7fffe24【参考方案3】:

使用自定义 JsonConverter 的更简单、完整的解决方案

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

public class CustomDictionaryConverter<TKey, TValue> : JsonConverter

    public override bool CanConvert(Type objectType) => objectType == typeof(Dictionary<TKey, TValue>);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        => serializer.Serialize(writer, ((Dictionary<TKey, TValue>)value).ToList());

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        => serializer.Deserialize<KeyValuePair<TKey, TValue>[]>(reader).ToDictionary(kv => kv.Key, kv => kv.Value);

用法:

[JsonConverter(typeof(CustomDictionaryConverter<KeyType, ValueType>))]
public Dictionary<KeyType, ValueType> MyDictionary;

【讨论】:

太好了,谢谢。请注意——valueWriteJson 中可以为空,所以我添加了小检查——if (value == null) serializer.Serialize(writer, value); else serializer.Serialize(writer, ((Dictionary&lt;TKey, TValue&gt;)value).ToList())

以上是关于如何使用 Json.Net 序列化/反序列化带有自定义键的字典?的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 JSON.NET 反序列化带有 [] 字符的 JSON

如何使用 Json.NET 反序列化高精度十进制值?

使用带有 ItemRequired = Required.Always 的 Json.Net 反序列化时忽略属性

使用 Json.NET 自定义反序列化

如何使用json.net自定义反序列化为对象

使用 JSON.NET 反序列化自定义 null 处理