2013-08-22 10 views
7

Jak mogę zrobić serializator Json.NET do serializacji instancji IDictionary<,> do tablicy obiektów z właściwościami klucz/wartość? Domyślnie serializuje wartość klucza do nazwy właściwości obiektu JSON.Serializuj słownik <,> jako tablicę w Json.NET

Zasadniczo muszę coś takiego:

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

zamiast:

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

Próbowałem dodać KeyValuePairConverter do ustawień serializer ale to nie ma znaczenia. (Uważam, że ten konwerter jest ignorowany na typ IDictionary<> ale nie można łatwo zmienić typ mojego obiektów jak są one odbierane z innych bibliotek, więc zmiana z IDictionary<> do ICollection<KeyValuePair<>> nie jest opcja dla mnie.)

Odpowiedz

5

Mogłem uruchom ten konwerter.

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(); 
    } 
} 

Oto przykład użycia konwertera:

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); 

I tu jest wyjście z powyższym:

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

Byłoby łatwiej (i bardziej kompletny), aby sprawdzić, czy nie-generycznego 'System.Collections.IDictionary' zamiast. Ogólne interfejsy generalnie rozszerzają ogólne interfejsy. Ponadto twoja metoda 'TypeImplementsGenericInterface()' może zostać uproszczona za pomocą 'Any()' zamiast 'Where(). FirstOrDefault()! = Null'. –

+0

Chociaż byłoby miło, 'IDictionary ' faktycznie NIE rozszerza nietypowego interfejsu 'IDictionary'. Zobacz dokumentację (http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx). Jednak ze względu na kompletność, zredagowałem swoją odpowiedź, aby dodać obsługę 'IDictionary' i włączyłem twoją drugą sugestię na temat uproszczenia metody' TypeImplementsGenericInterface() '. Dzięki! –

+0

Ach tak, zapomniałem o tym. Correction, _immutable_ generic interfaces generalnie rozszerza odpowiedni _immutable_ nie-rodzajowy interfejs, niestety nie ma tu zastosowania. –

2

zorientowali się w inny sposób - można tworzyć niestandardowe ContractResolver i ustaw go na JsonSerializerSettings przed serializacją (de). Poniższa jest wyprowadzona z wbudowanego CamelCasePropertyNamesContractResolver w celu przekształcenia serializowanych nazw właściwości na wielbłądzie, ale może być wyprowadzona z DefaultContractResolver, jeśli nie chcesz modyfikować nazw.

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); 
    } 
} 

Zastosowanie:

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

Chociaż twój kod działa w celu serializacji, nie jest w stanie deserializować. Ta odpowiedź działa w obu przypadkach: http://stackoverflow.com/a/25064637/2528649 – neleus