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

时间:2022-08-29 16:05:04

I have the following class, that I use as a key in a dictionary:

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

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

The test I am running is here:

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

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

The test fails, because Json.Net seems to be using the ToString() method on the dictionary keys, instead of serializing them properly. The resulting json from the test above is:

测试失败,因为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
}

which is clearly wrong. How can I get it to work?

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

2 个解决方案

#1


10  

This should do the trick:

这应该是诀窍:

Serialization:

连载:

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

By calling expected.ToArray() you're serializing an array of KeyValuePair<MyClass, object> objects rather than the dictionary.

通过调用expected.ToArray(),您将序列化KeyValuePair 对象的数组,而不是字典。 ,object>

Deserialization:

反序列化:

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

Here you deserialize the array and then retrieve the dictionary with .ToDictionary(...) call.

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

I'm not sure if the output meets your expectations, but surely it passes the equality assertion.

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

#2


5  

Grx70's answer is good - just adding an alternative solution here. I ran into this problem in a Web API project where I wasn't calling SerializeObject but allowing the serialization to happen automagically.

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

This custom JsonConverter based on Brian Rogers' answer to a similar question did the trick for me:

基于Brian Rogers对类似问题的回答的这个自定义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();
    }
}

In my case, I was serializing a Dictionary<MyCustomType, int> property on a class where MyCustomType had properties like Name and Id. This is the result:

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

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

#1


10  

This should do the trick:

这应该是诀窍:

Serialization:

连载:

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

By calling expected.ToArray() you're serializing an array of KeyValuePair<MyClass, object> objects rather than the dictionary.

通过调用expected.ToArray(),您将序列化KeyValuePair 对象的数组,而不是字典。 ,object>

Deserialization:

反序列化:

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

Here you deserialize the array and then retrieve the dictionary with .ToDictionary(...) call.

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

I'm not sure if the output meets your expectations, but surely it passes the equality assertion.

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

#2


5  

Grx70's answer is good - just adding an alternative solution here. I ran into this problem in a Web API project where I wasn't calling SerializeObject but allowing the serialization to happen automagically.

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

This custom JsonConverter based on Brian Rogers' answer to a similar question did the trick for me:

基于Brian Rogers对类似问题的回答的这个自定义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();
    }
}

In my case, I was serializing a Dictionary<MyCustomType, int> property on a class where MyCustomType had properties like Name and Id. This is the result:

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

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