2015-02-11 99 views
11

我有一個序列化到DataContractJsonSerializer存儲的字典,我想用Newtonsoft.Json反序列化。Newtonsoft Json反序列化字典作爲DataContractJsonSerializer的鍵/值列表

的DataContractJsonSerializer已序列化的字典鍵/值對的列表:

{"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"]); 
    } 

回答

7

您可以使用此自定義轉換,這取決於什麼記號字典開頭,反序列化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屬性裝點在DataDict屬性:

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

然後反序列化這兩個字符串應該按預期工作。

+0

謝謝你,完美的工作! (我用TypeConverter進行了實驗,但沒有成功,但是這樣做:) – 2015-02-12 09:06:45

18

擴展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); 
+1

謝謝!那非常漂亮!我想借此機會鼓勵您嘗試將其加入到Newtonsoft.Json中,因爲我認爲javascriptserializer/data合約序列化程序和Newtonsoft.Json之間的通用兼容性會很好! – 2015-02-21 18:05:33

+3

這應該是接受的答案... – Lightning3 2015-12-08 15:24:05

1

擴展這一更爲而佔各類鑄件(例如,Enum與IComparable的IDictionary),包括帶有隱式運算符的類型,您可以參考我的實現,它緩存跨請求類型的分辨率。

// ---------------------- JSON Converter -------------------- -----------

/// <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; 
    } 
相關問題