【问题标题】:How can I serialize/deserialize a dictionary with custom keys using Json.Net?如何使用 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,显然不是。

标签: c# .net json json.net


【解决方案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
        ]
    ]
    ...
    

    【讨论】:

    【解决方案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())
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-19
    • 1970-01-01
    • 2017-06-30
    相关资源
    最近更新 更多