我有以下类,我将其用作字典中的键:
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
}
显然是错误的。我怎样才能使它工作呢?
这应该能奏效:
序列化: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(...)
调用检索字典。
我不确定输出是否符合您的期望,但肯定它通过了相等断言。
Grx70的答案很好-只是在这里添加了一个替代解决方案。我在一个Web API项目中遇到了这个问题,我没有调用SerializeObject
,但允许序列化自动发生。
这个基于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();
}
}
在我的例子中,我在一个类上序列化Dictionary<MyCustomType, int>
属性,其中MyCustomType
具有Name
和Id
的属性。结果如下:
...
"dictionaryProp": [
[
{
"name": "MyCustomTypeInstance1.Name",
"description": null,
"id": null
},
3
],
[
{
"name": "MyCustomTypeInstance2.Name",
"description": null,
"id": null
},
2
]
]
...
更简单,完整的解决方案,使用自定义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;
由于您的类可以轻松地序列化和反序列化为普通字符串,因此可以使用自定义Json转换器完成此操作,同时保持Json的对象结构。
我为此目的编写了一个JsonConverter,以对象样式转换任何字典,而无需使用数组或自定义键类型的类型参数:Json。对象样式
自定义键字典的。NET转换器要点是手动检查键-值对,并强制对源自Json对象属性的键类型进行序列化。我能给出的最简约的工作示例:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Aquire reflection info & get key-value-pairs:
Type type = value.GetType();
bool isStringKey = type.GetGenericArguments()[0] == typeof(string);
IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
IEnumerator valueEnumerator = values.GetEnumerator();
// Write each key-value-pair:
StringBuilder sb = new StringBuilder();
using (StringWriter tempWriter = new StringWriter(sb))
{
writer.WriteStartObject();
foreach (object key in keys)
{
valueEnumerator.MoveNext();
// convert key, force serialization of non-string keys
string keyStr = null;
if (isStringKey)
{
// Key is not a custom type and can be used directly
keyStr = (string)key;
}
else
{
sb.Clear();
serializer.Serialize(tempWriter, key);
keyStr = sb.ToString();
// Serialization can wrap the string with literals
if (keyStr[0] == '"' && keyStr[str.Length-1] == '"')
keyStr = keyStr.Substring(1, keyStr.Length - 1);
// TO-DO: Validate key resolves to single string, no complex structure
}
writer.WritePropertyName(keyStr);
// default serialize value
serializer.Serialize(writer, valueEnumerator.Current);
}
writer.WriteEndObject();
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Aquire reflection info & create resulting dictionary:
Type[] dictionaryTypes = objectType.GetGenericArguments();
bool isStringKey = dictionaryTypes[0] == typeof(string);
IDictionary res = Activator.CreateInstance(objectType) as IDictionary;
// Read each key-value-pair:
object key = null;
object value = null;
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject)
break;
if (reader.TokenType == JsonToken.PropertyName)
{
key = isStringKey ? reader.Value : serializer.Deserialize(reader, dictionaryTypes[0]);
}
else
{
value = serializer.Deserialize(reader, dictionaryTypes[1]);
res.Add(key, value);
key = null;
value = null;
}
}
return res;
}
使用这样的转换器,JSON对象可以直接用作字典,正如您所期望的那样。换句话说,现在可以这样做:
{
MyDict: {
"Key1": "Value1",
"Key2": "Value2"
[...]
}
}
而不是:
{
MyDict: [
["Key1", "Value1"],
["Key2", "Value2"]
[...]
]
}