2016-07-07 15 views
5

Mam klasę ogólną, która zawiera właściwość publiczną, która jest interfejsem generycznym tego samego typu, co klasa nadrzędna. Przykładowy kod poniżej.Deserializowanie JSON do ogólnej właściwości interfejsu

public interface IExample<T> 
{ 
    T Value { get; set; } 
    string Name { get; set; } 
} 

public class Example<T> : IExample<T> 
{ 
    public string Name { get; set; } 
    public T Value { get; set; } 
} 

public class Parent<T> 
{ 
    public string ParentName { get; set; } 
    public IExample<T> ExampleItem { get; set; } 
} 

public class MainClass 
{ 
    public Parent<int> IntParent { get; set; } 
} 

Używam Json.NET do serializacji obiektu MainClass który może zawierać wiele Parent<T> obiektów. Parent<T> może być dowolnym typem bez ograniczeń typu. Jednak nie mogę zdegradować wynikowego JSON w sposób ogólny.

Podjęto próbę utworzenia JsonConverter dla deserializera JSON.net, ale nie mogę znaleźć sposobu, aby zastosować go ogólnie. Przykład poniżej: JsonConverter.

public class InterfaceJsonConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface, new() 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return (typeof(TInterface) == objectType); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return serializer.Deserialize<TImplementation>(reader); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     serializer.Serialize(writer, value); 
    } 
} 

Powyższy konwerter pozwoli atrybutem być umieszczone na posesji ExampleItem z Parent<T> klasy jak:

public class Parent<T> 
{ 
    public string ParentName { get; set; } 
    [JsonConverter(typeof(InterfaceJsonConverter<IExample<T>, Example<T>>))] 
    public IExample<T> ExampleItem { get; set; } 
} 

Ale C# nie pozwalają mieć ogólne odniesienia typu w atrybutach (ze względu na charakter atrybutów i refleksji). Jedynym rozwiązaniem, jakie do tej pory wymyśliłem, jest ręczne dodanie do serializera nowego InterfaceJsonConverter dla każdego oczekiwanego typu T przed wywołaniem metody Deserialize(). Ogranicza to jednak możliwe typy Parent<T>, ponieważ każdy typ należy dodać ręcznie, jeśli można go przekształcić w postać szeregową.

Czy istnieje sposób deserializacji tego w sposób ogólny? Czy istnieje inne podejście, które powinienem podjąć?

Odpowiedz

3

Można to wykonać, w sposób pośredni, poprzez przepuszczanie open generic typetypeof(Example<>) do odpowiedniego JsonConverter jako argument konstruktora, a wewnątrz ReadJson() skonstruowania odpowiedniego zamkniętego ogólny przyjmując objectType przekazywane w ma takie same parametry ogólne, jak pożądane betonu zamknięty rodzaj ogólny.

Należy również pamiętać, że dopóki konwerter jest stosowany bezpośrednio do właściwości przy użyciu [JsonConverter(Type,Object[])], konwerter nie musi znać typu interfejsu, ponieważ CanConvert() nie zostanie wywołany. CanConvert() jest wywoływana tylko wtedy, gdy konwerter znajduje się na liście JsonSerializer.Converters.

Zatem Twój konwerter staje:

public class InterfaceToConcreteGenericJsonConverter : JsonConverter 
{ 
    readonly Type GenericTypeDefinition; 

    public InterfaceToConcreteGenericJsonConverter(Type genericTypeDefinition) 
    { 
     if (genericTypeDefinition == null) 
      throw new ArgumentNullException(); 
     this.GenericTypeDefinition = genericTypeDefinition; 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     throw new NotImplementedException(); 
    } 

    Type MakeGenericType(Type objectType) 
    { 
     if (!GenericTypeDefinition.IsGenericTypeDefinition) 
      return GenericTypeDefinition; 
     try 
     { 
      var parameters = objectType.GetGenericArguments(); 
      return GenericTypeDefinition.MakeGenericType(parameters); 
     } 
     catch (Exception ex) 
     { 
      // Wrap the reflection exception in something more useful. 
      throw new JsonSerializationException(string.Format("Unable to construct concrete type from generic {0} and desired type {1}", GenericTypeDefinition, objectType), ex); 
     } 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return serializer.Deserialize(reader, MakeGenericType(objectType)); 
    } 

    public override bool CanWrite { get { return false; } } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Stosuje się go w następujący sposób:

public class Parent<T> 
{ 
    public string ParentName { get; set; } 

    [JsonConverter(typeof(InterfaceToConcreteGenericJsonConverter), new object[] { typeof(Example<>) })] 
    public IExample<T> ExampleItem { get; set; } 
} 

Aby zastosować taki konwerter z parametrami do elementów kolekcji, należy JsonPropertyAttribute.ItemConverterType i JsonPropertyAttribute.ItemConverterParameters, np:

public class Parent<T> 
{ 
    public string ParentName { get; set; } 

    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteGenericJsonConverter), ItemConverterParameters = new object[] { typeof(Example<>) })] 
    public List<IExample<T>> ExampleList { get; set; } 
} 
+1

To działało! Nie myślałem o użyciu odbicia wewnątrz konwertera - świetne rozwiązanie. Dziękuję Ci! – jeff17237

Powiązane problemy