Newtonsoft Json将字典反序列化为DataContractJsonSerializer中的键/值列表

我有一个字典序列化存储与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 Dict { get; set; } } [TestMethod] public void TestSerializeDataContractDeserializeNewtonsoftDictionary() { var d = new Data { Dict = new Dictionary { {"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(newJson); Assert.AreEqual("Val1", d2.Dict["Key1"]); Assert.AreEqual("Val2", d2.Dict["Key2"]); var d3 = JsonConvert.DeserializeObject(oldJson); // Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (eg [1,2,3]) into // type 'System.Collections.Generic.IDictionary`2[System.String,System.String]' because the type requires a JSON // object (eg {"name":"value"}) to deserialize correctly. // // To fix this error either change the JSON to a JSON object (eg {"name":"value"}) or change the deserialized type // to an array or a type that implements a collection interface (eg ICollection, IList) like List 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"]); } 

您可以使用自定义转换器,具体取决于字典开头的标记,将其反序列化为JSON.NET的默认方式,或者将其反序列化为数组,然后将该数组转换为Dictionary

 public class DictionaryConverter : JsonConverter { public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { IDictionary 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) serializer.Deserialize(reader, typeof(IDictionary)); } return result; } public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } public override bool CanConvert(Type objectType) { return typeof(IDictionary).IsAssignableFrom(objectType); } public override bool CanWrite { get { return false; } } } 

然后,您可以使用JsonConverter属性JsonConverter Data类中的JsonConverter属性:

 public sealed class Data { [JsonConverter(typeof(DictionaryConverter))] public IDictionary Dict { get; set; } } 

然后反序列化两个字符串应该按预期工作。

扩展Andrew Whitaker的答案 ,这是一个完全通用的版本,适用于任何类型的可写字典:

 public class JsonGenericDictionaryOrArrayConverter: JsonConverter { public override bool CanConvert(Type objectType) { return objectType.GetDictionaryKeyValueTypes().Count() == 1; } public override bool CanWrite { get { return false; } } object ReadJsonGeneric(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var tokenType = reader.TokenType; var dict = existingValue as IDictionary; if (dict == null) { var contract = serializer.ContractResolver.ResolveContract(objectType); dict = (IDictionary)contract.DefaultCreator(); } if (tokenType == JsonToken.StartArray) { var pairs = new JsonSerializer().Deserialize[]>(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 { ///  /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface. ///  ///  ///  public static IEnumerable 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> 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(args[0], args[1]); } } } } 

然后就像使用它一样

  var settings = new JsonSerializerSettings { Converters = new JsonConverter[] {new JsonGenericDictionaryOrArrayConverter() } }; var d2 = JsonConvert.DeserializeObject(newJson, settings); var d3 = JsonConvert.DeserializeObject(oldJson, settings); 

在考虑类型转换(例如,Enum与IComparable的IDictionary)(包括具有隐式运算符的类型)时,进一步扩展这一点,您可以参考我的实现,该实现缓存了跨请求的类型解析。

// ———————- JSON转换器———————— ——-

 /// Deserializes dictionaries. public class DictionaryConverter : JsonConverter { private static readonly System.Collections.Concurrent.ConcurrentDictionary> resolvedTypes = new System.Collections.Concurrent.ConcurrentDictionary>(); /// If this converter is able to handle a given conversion. /// The type to be handled. /// Returns if this converter is able to handle a given conversion. 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; } /// Converts from serialized to object. /// The reader. /// The destination type. /// The existing value. /// The serializer. /// Returns the deserialized instance as per the actual target type. 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(keyType, valueType); } // Load JObject from stream var jObject = JObject.Load(reader); return jObject.Children() .OfType() .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); } /// Serializes an object with default settings. /// The writer. /// The value to write. /// The serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } } 

// ——————–使用的扩展方法————————-

  ///  /// Indicates if a particular object instance at some point inherits from a specific type or implements a specific interface. ///  /// The System.Type to be evaluated. /// The System.Type to test for. /// Returns a boolean indicating if a particular object instance at some point inherits from a specific type or implements a specific interface. 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; } /// Casts an object to another type. /// The object to cast. /// The end type to cast to. /// Returns the casted object. 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); } /// Creates a late-bound dictionary. /// The type of elements. /// The enumeration. /// The function that produces the key. /// The type of key. /// The function that produces the value. /// The type of value. /// Returns the late-bound typed dictionary. public static IDictionary ToDictionary(this IEnumerable enumeration, Func keySelector, Type keyType, Func 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; }