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

Grz*_*nio 7 .net c# json json.net

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

    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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

    [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);
    }
Run Code Online (Sandbox Code Playgroud)

测试失败,因为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
}
Run Code Online (Sandbox Code Playgroud)

这显然是错的.我怎样才能让它发挥作用?

Grx*_*x70 11

这应该做的伎俩:

连载:

JsonConvert.SerializeObject(expected.ToArray(), Formatting.Indented, jsonSerializerSettings);
Run Code Online (Sandbox Code Playgroud)

通过调用expected.ToArray()你序列化一个KeyValuePair<MyClass, object>对象数组而不是字典.

反序列化:

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

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

我不确定输出是否符合您的期望,但肯定会通过相等的断言.


Nat*_*ini 7

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

JsonConverter基于Brian Rogers对类似问题的回答的这个习惯对我有用:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的例子中,我Dictionary<MyCustomType, int>在一个MyCustomType具有类似Name和的属性的类上序列化一个属性Id.这是结果:

...
"dictionaryProp": [
    [
      {
        "name": "MyCustomTypeInstance1.Name",
        "description": null,
        "id": null
      },
      3
    ],
    [
      {
        "name": "MyCustomTypeInstance2.Name",
        "description": null,
        "id": null
      },
      2
    ]
]
...
Run Code Online (Sandbox Code Playgroud)

  • 这是具有反序列化的要点:https://gist.github.com/SteveDunn/e355b98b0dbf5a0209cb8294f7fffe24 (2认同)

Ger*_*set 6

使用自定义 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);
}
Run Code Online (Sandbox Code Playgroud)

用法:

[JsonConverter(typeof(CustomDictionaryConverter<KeyType, ValueType>))]
public Dictionary<KeyType, ValueType> MyDictionary;
Run Code Online (Sandbox Code Playgroud)

  • 太好了谢谢。请注意 - 在 `WriteJson` 中 `value` 可以为 null,因此我添加了一个小检查 - `if (value == null) serializer.Serialize(writer, value); else serializer.Serialize(writer, ((Dictionary&lt;TKey, TValue&gt;)value).ToList())` (3认同)