2013-08-22 16 views
7

IDictionary<,>インスタンスをキー/値プロパティを持つオブジェクトの配列にシリアル化するJson.NETシリアライザを作成するにはどうすればよいですか? デフォルトでは、Keyの値をJSONオブジェクトのプロパティ名にシリアル化します。Json.NETで辞書<,>を配列として配列

[{"key":"some key","value":1},{"key":"another key","value":5}] 

の代わり:

基本的に私はこのような何か必要

{{"some key":1},{"another key":5}} 

を私はシリアライザの設定にKeyValuePairConverterを追加しようとしましたが、それは効果がありません。 (私はこのコンバータはIDictionary<>のタイプでは無視されますが、彼らは他の図書館から受信されているように私は簡単に私のオブジェクトの種類を変更することはできませんので、IDictionary<>からICollection<KeyValuePair<>>に変更すると私のためのオプションではありません見つける。)

答えて

5

私がすることができましたこのコンバータを動作させる。ここで

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using Newtonsoft.Json; 
using Newtonsoft.Json.Converters; 

public class CustomDictionaryConverter : 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.WriteStartObject(); 
      writer.WritePropertyName("key"); 
      writer.WriteValue(key); 
      writer.WritePropertyName("value"); 
      serializer.Serialize(writer, valueEnumerator.Current); 
      writer.WriteEndObject(); 
     } 
     writer.WriteEndArray(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

コンバータを使用する例です:

IDictionary<string, int> dict = new Dictionary<string, int>(); 
dict.Add("some key", 1); 
dict.Add("another key", 5); 

string json = JsonConvert.SerializeObject(dict, new CustomDictionaryConverter()); 
Console.WriteLine(json); 

そして、ここでは、上記の出力である:

[{"key":"some key","value":1},{"key":"another key","value":5}] 
+1

それは、代わりに非ジェネリック 'System.Collections.IDictionary'をチェックしやすい(と、より完全な)になります。ジェネリックインターフェイスは、一般的に非ジェネリックインターフェイスを拡張します。また、 'TypeImplementsGenericInterface()'メソッドは 'Where()。FirstOrDefault()!= null'の代わりに' Any() 'を使って単純化することができます。 –

+0

いいですが、 'IDictionary 'は実際には非ジェネリックな 'IDictionary'インターフェースを拡張しません。 [documentation](http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx)を参照してください。しかし、完全性のために、私は 'IDictionary'のサポートを追加する答えを編集し、' TypeImplementsGenericInterface() 'メソッドを単純化するというあなたの提案を取り入れました。ありがとう! –

+0

ああ、私はそれを忘れていました。修正、_immutable_一般的なインターフェイスは一般的に対応する_immutable_非ジェネリックインターフェイスを拡張しますが、残念ながらここでは適用されません。これは他の誰にとっても役に立ちます。 –

2

は、別の方法を考え出した - カスタムContractResolverを作成することができますし、シリアライズする前にJsonSerializerSettingsに設定してください。下の例は、組み込みのCamelCasePropertyNamesContractResolverからシリアル化されたプロパティ名をキャメルケースに変換したものですが、名前を変更したくない場合はDefaultContractResolverから派生させることができます。

public class DictionaryFriendlyContractResolver : CamelCasePropertyNamesContractResolver 
{ 
    protected override JsonContract CreateContract(Type objectType) 
    { 
     if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) 
      return new JsonArrayContract(objectType); 
     if (objectType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))) 
      return new JsonArrayContract(objectType); 
     return base.CreateContract(objectType); 
    } 
} 

使用法:

var cfg = new JsonSerializerSettings(); 
cfg.ContractResolver = new DictionaryFriendlyContractResolver(); 
string json = JsonConvert.SerializeObject(myModel, cfg); 
+0

あなたのコードはシリアライゼーションのために機能しますが、デシリアライズすることはできません。この回答は両方のケースで有効です。http://stackoverflow.com/a/25064637/2528649 – neleus