Newtonsoft Json 将字典反序列化为来自 DataContractJsonSerializer 的键/值列表
Posted
技术标签:
【中文标题】Newtonsoft Json 将字典反序列化为来自 DataContractJsonSerializer 的键/值列表【英文标题】:Newtonsoft Json Deserialize Dictionary as Key/Value list from DataContractJsonSerializer 【发布时间】:2015-04-11 16:25:57 【问题描述】:我有一个使用 DataContractJsonSerializer 序列化到存储的字典,我想使用 Newtonsoft.Json 对其进行反序列化。
DataContractJsonSerializer 已将 Dictionary 序列化为键/值对列表:
"Dict":["Key":"Key1","Value":"Val1","Key":"Key2","Value":"Val2"]
我可以给JsonConvert.DeserializeObject<>()
提供什么很酷的选项,使其支持该数据格式和来自 Newtonsoft.Json 的格式?
"Dict":"Key1":"Val1","Key2":"Val2"
是 Newtonsoft.Json 创建的漂亮格式,我希望在过渡期间能够同时读取旧的 DataContract 格式和新的 Newtonsoft 格式。
简化示例:
//[JsonArray]
public sealed class Data
public IDictionary<string, string> Dict get; set;
[TestMethod]
public void TestSerializeDataContractDeserializeNewtonsoftDictionary()
var d = new Data
Dict = new Dictionary<string, string>
"Key1", "Val1",
"Key2", "Val2",
;
var oldJson = String.Empty;
var formatter = new DataContractJsonSerializer(typeof (Data));
using (var stream = new MemoryStream())
formatter.WriteObject(stream, d);
oldJson = Encoding.UTF8.GetString(stream.ToArray());
var newJson = JsonConvert.SerializeObject(d);
// [JsonArray] on Data class gives:
//
// System.InvalidCastException: Unable to cast object of type 'Data' to type 'System.Collections.IEnumerable'.
Console.WriteLine(oldJson);
// This is tha data I have in storage and want to deserialize with Newtonsoft.Json, an array of key/value pairs
// "Dict":["Key":"Key1","Value":"Val1","Key":"Key2","Value":"Val2"]
Console.WriteLine(newJson);
// This is what Newtonsoft.Json generates and should also be supported:
// "Dict":"Key1":"Val1","Key2":"Val2"
var d2 = JsonConvert.DeserializeObject<Data>(newJson);
Assert.AreEqual("Val1", d2.Dict["Key1"]);
Assert.AreEqual("Val2", d2.Dict["Key2"]);
var d3 = JsonConvert.DeserializeObject<Data>(oldJson);
// Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into
// type 'System.Collections.Generic.IDictionary`2[System.String,System.String]' because the type requires a JSON
// object (e.g. "name":"value") to deserialize correctly.
//
// To fix this error either change the JSON to a JSON object (e.g. "name":"value") or change the deserialized type
// to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be
// deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from
// a JSON array.
//
// Path 'Dict', line 1, position 9.
Assert.AreEqual("Val1", d3.Dict["Key1"]);
Assert.AreEqual("Val2", d3.Dict["Key2"]);
【问题讨论】:
【参考方案1】:扩展Andrew Whitaker's answer,这是一个完全通用的版本,适用于任何类型的可写字典:
public class JsonGenericDictionaryOrArrayConverter: JsonConverter
public override bool CanConvert(Type objectType)
return objectType.GetDictionaryKeyValueTypes().Count() == 1;
public override bool CanWrite get return false;
object ReadJsonGeneric<TKey, TValue>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var tokenType = reader.TokenType;
var dict = existingValue as IDictionary<TKey, TValue>;
if (dict == null)
var contract = serializer.ContractResolver.ResolveContract(objectType);
dict = (IDictionary<TKey, TValue>)contract.DefaultCreator();
if (tokenType == JsonToken.StartArray)
var pairs = new JsonSerializer().Deserialize<KeyValuePair<TKey, TValue>[]>(reader);
if (pairs == null)
return existingValue;
foreach (var pair in pairs)
dict.Add(pair);
else if (tokenType == JsonToken.StartObject)
// Using "Populate()" avoids infinite recursion.
// https://github.com/JamesNK/Newtonsoft.Json/blob/ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs
serializer.Populate(reader, dict);
return dict;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var keyValueTypes = objectType.GetDictionaryKeyValueTypes().Single(); // Throws an exception if not exactly one.
var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(new[] keyValueTypes.Key, keyValueTypes.Value );
return genericMethod.Invoke(this, new object [] reader, objectType, existingValue, serializer );
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
public static class TypeExtensions
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] type .Concat(type.GetInterfaces());
else
return type.GetInterfaces();
public static IEnumerable<KeyValuePair<Type, Type>> GetDictionaryKeyValueTypes(this Type type)
foreach (Type intType in type.GetInterfacesAndSelf())
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
var args = intType.GetGenericArguments();
if (args.Length == 2)
yield return new KeyValuePair<Type, Type>(args[0], args[1]);
然后像这样使用它
var settings = new JsonSerializerSettings Converters = new JsonConverter[] new JsonGenericDictionaryOrArrayConverter() ;
var d2 = JsonConvert.DeserializeObject<Data>(newJson, settings);
var d3 = JsonConvert.DeserializeObject<Data>(oldJson, settings);
【讨论】:
谢谢!那非常漂亮!我想借此机会鼓励您尝试将其引入 Newtonsoft.Json,因为我认为 javascriptserializer/数据合约序列化程序和 Newtonsoft.Json 之间的一般兼容性会很棒!【参考方案2】:您可以为此使用自定义转换器,具体取决于字典以什么标记开头,将其反序列化为 JSON.NET 的默认方式,或将其反序列化为数组,然后将该数组转换为 Dictionary
:
public class DictionaryConverter : JsonConverter
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
IDictionary<string, string> result;
if (reader.TokenType == JsonToken.StartArray)
JArray legacyArray = (JArray)JArray.ReadFrom(reader);
result = legacyArray.ToDictionary(
el => el["Key"].ToString(),
el => el["Value"].ToString());
else
result =
(IDictionary<string, string>)
serializer.Deserialize(reader, typeof(IDictionary<string, string>));
return result;
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
public override bool CanConvert(Type objectType)
return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
public override bool CanWrite
get return false;
然后,您可以使用JsonConverter
属性来装饰Data
类中的Dict
属性:
public sealed class Data
[JsonConverter(typeof(DictionaryConverter))]
public IDictionary<string, string> Dict get; set;
然后反序列化两个字符串应该按预期工作。
【讨论】:
谢谢,效果很好! (我尝试了一个不起作用的 TypeConverter,但这确实有效:)【参考方案3】:在考虑类型转换(例如,Enum 与 IComparable 的 IDictionary)的同时进一步扩展此功能,包括具有隐式运算符的类型,您可以参考我的实现,该实现缓存跨请求的类型解析。
//------------------------ JSON 转换器 --------- ----------
/// <summary>Deserializes dictionaries.</summary>
public class DictionaryConverter : JsonConverter
private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<Type, Type>> resolvedTypes = new System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<Type, Type>>();
/// <summary>If this converter is able to handle a given conversion.</summary>
/// <param name="objectType">The type to be handled.</param>
/// <returns>Returns if this converter is able to handle a given conversion.</returns>
public override bool CanConvert(Type objectType)
if (resolvedTypes.ContainsKey(objectType)) return true;
var result = typeof(IDictionary).IsAssignableFrom(objectType) || objectType.IsOfType(typeof(IDictionary));
if (result) //check key is string or enum because it comes from Jvascript object which forces the key to be a string
if (objectType.IsGenericType && objectType.GetGenericArguments()[0] != typeof(string) && !objectType.GetGenericArguments()[0].IsEnum)
result = false;
return result;
/// <summary>Converts from serialized to object.</summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The destination type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
/// <returns>Returns the deserialized instance as per the actual target type.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
Type keyType = null;
Type valueType = null;
if (resolvedTypes.ContainsKey(objectType))
keyType = resolvedTypes[objectType].Item1;
valueType = resolvedTypes[objectType].Item2;
else
//dictionary type
var dictionaryTypes = objectType.GetInterfaces()
.Where(z => z == typeof(IDictionary) || z == typeof(IDictionary<,>))
.ToList();
if (objectType.IsInterface)
dictionaryTypes.Add(objectType);
else
dictionaryTypes.Insert(0, objectType);
var dictionaryType = dictionaryTypes.Count == 1
? dictionaryTypes[0]
: dictionaryTypes.Where(z => z.IsGenericTypeDefinition)
.FirstOrDefault();
if (dictionaryType == null) dictionaryTypes.First();
keyType = !dictionaryType.IsGenericType
? typeof(object)
: dictionaryType.GetGenericArguments()[0];
valueType = !dictionaryType.IsGenericType
? typeof(object)
: dictionaryType.GetGenericArguments()[1];
resolvedTypes[objectType] = new Tuple<Type, Type>(keyType, valueType);
// Load JObject from stream
var jObject = JObject.Load(reader);
return jObject.Children()
.OfType<JProperty>()
.Select(z => new Key = z.Name, Value = serializer.Deserialize(z.Value.CreateReader(), valueType) )
.Select(z => new
Key = keyType.IsEnum
? System.Enum.Parse(keyType, z.Key)
: z.Key,
Value = z.Value.Cast(valueType)
)
.ToDictionary(z => z.Key, keyType, w => w.Value, valueType);
/// <summary>Serializes an object with default settings.</summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value to write.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
serializer.Serialize(writer, value);
//-------------------- 使用的扩展方法 ---------- ---
/// <summary>
/// Indicates if a particular object instance at some point inherits from a specific type or implements a specific interface.
/// </summary>
/// <param name="sourceType">The System.Type to be evaluated.</param>
/// <param name="typeToTestFor">The System.Type to test for.</param>
/// <returns>Returns a boolean indicating if a particular object instance at some point inherits from a specific type or implements a specific interface.</returns>
public static bool IsOfType(this System.Type sourceType, System.Type typeToTestFor)
if (baseType == null) throw new System.ArgumentNullException("baseType", "Cannot test if object IsOfType() with a null base type");
if (targetType == null) throw new System.ArgumentNullException("targetType", "Cannot test if object IsOfType() with a null target type");
if (object.ReferenceEquals(baseType, targetType)) return true;
if (targetType.IsInterface)
return baseType.GetInterfaces().Contains(targetType)
? true
: false;
while (baseType != null && baseType != typeof(object))
baseType = baseType.BaseType;
if (baseType == targetType)
return true;
return false;
/// <summary>Casts an object to another type.</summary>
/// <param name="obj">The object to cast.</param>
/// <param name="type">The end type to cast to.</param>
/// <returns>Returns the casted object.</returns>
public static object Cast(this object obj, Type type)
var dataParam = Expression.Parameter(obj == null ? typeof(object) : obj.GetType(), "data");
var body = Expression.Block(Expression.Convert(dataParam, type));
var run = Expression.Lambda(body, dataParam).Compile();
return run.DynamicInvoke(obj);
/// <summary>Creates a late-bound dictionary.</summary>
/// <typeparam name="T">The type of elements.</typeparam>
/// <param name="enumeration">The enumeration.</param>
/// <param name="keySelector">The function that produces the key.</param>
/// <param name="keyType">The type of key.</param>
/// <param name="valueSelector">The function that produces the value.</param>
/// <param name="valueType">The type of value.</param>
/// <returns>Returns the late-bound typed dictionary.</returns>
public static IDictionary ToDictionary<T>(this IEnumerable<T> enumeration, Func<T, object> keySelector, Type keyType, Func<T, object> valueSelector, Type valueType)
if (enumeration == null) return null;
var dictionaryClosedType = typeof(Dictionary<,>).MakeGenericType(new Type[] keyType, valueType );
var dictionary = dictionaryClosedType.CreateInstance() as IDictionary;
enumeration.ForEach(z => dictionary.Add(keySelector(z), valueSelector(z)));
return dictionary;
【讨论】:
以上是关于Newtonsoft Json 将字典反序列化为来自 DataContractJsonSerializer 的键/值列表的主要内容,如果未能解决你的问题,请参考以下文章
csharp 使用Newtonsoft JSON.NET将任何对象序列化/反序列化为JSON
使用 Newtonsoft 将 JSON 反序列化为 .NET 对象(或者可能是 LINQ to JSON?)