如何使用 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<MyClass, object>
对象数组而不是字典。
反序列化:
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<MyCustomType, int>
属性,其中MyCustomType
具有Name
和Id
之类的属性。结果如下:
...
"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;
【讨论】:
太好了,谢谢。请注意——value
在WriteJson
中可以为空,所以我添加了小检查——if (value == null) serializer.Serialize(writer, value); else serializer.Serialize(writer, ((Dictionary<TKey, TValue>)value).ToList())
以上是关于如何使用 Json.Net 序列化/反序列化带有自定义键的字典?的主要内容,如果未能解决你的问题,请参考以下文章
尝试使用 JSON.NET 反序列化带有 [] 字符的 JSON