2016-02-10 22 views
6

Ho ereditato alcune eredit, richiedendo una personalizzazione JsonConverter per la deserializzazione. Sto usando un approccio molto semplice per ora in cui determino il tipo in base all'esistenza di determinate proprietà.Come deserializzare un elenco di elementi astratti senza passare convertitori a DeserializeObject?

Nota importante: nel mio codice attualenon posso toccare le DeserializeObject chiamate, vale a dire non posso aggiungere convertitori personalizzati lì. So che questo è in qualche modo un problema XY, e realizzo come tale la mia risposta potrebbe essere che ciò che voglio non è possibile. Per quanto posso dire questo rende la mia domanda leggermente diversa da this question.

Ecco un Repro della mia situazione:

abstract class Mammal { } 
class Cat : Mammal { public int Lives { get; set; } } 
class Dog : Mammal { public bool Drools { get; set; } } 
class Person 
{ 
    [JsonConverter(typeof(PetConverter))] 
    public Mammal FavoritePet { get; set; } 

    [JsonConverter(typeof(PetConverter))] 
    public List<Mammal> OtherPets { get; set; } 
} 

e questo è il convertitore personalizzato:

public class PetConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); } 
    public override bool CanWrite { get { return false; } } 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) return null; 
     JObject jsonObject = JObject.Load(reader); 
     if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer); 
     if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer); 
     return null; 
    } 
} 

Questo funziona bene per il FavoritePet, ma non tanto per il OtherPets perché è una lista . Ecco un modo per riprodurre il mio problema con i test NUnit:

[TestFixture] 
class MyTests 
{ 
    [Test] 
    public void CanSerializeAndDeserializeSingleItem() 
    { 
     var person = new Person { FavoritePet = new Cat { Lives = 9 } }; 
     var json = JsonConvert.SerializeObject(person); 
     var actual = JsonConvert.DeserializeObject<Person>(json); 
     Assert.That(actual.FavoritePet, Is.InstanceOf<Cat>()); 
    } 

    [Test] 
    public void CanSerializeAndDeserializeList() 
    { 
     var person = new Person { OtherPets = new List<Mammal> { new Cat { Lives = 9 } } }; 
     var json = JsonConvert.SerializeObject(person); 
     var actual = JsonConvert.DeserializeObject<Person>(json); 
     Assert.That(actual.OtherPets.Single(), Is.InstanceOf<Cat>()); 
    } 
} 

Quest'ultimo test è rosso perché:

Newtonsoft.Json.JsonReaderException: Errore durante la lettura jobject da JsonReader. L'elemento corrente di JsonReader non è un oggetto: StartArray. Percorso 'OtherPets', la linea 1, la posizione 33.

Ho anche provato, senza il convertitore personalizzato su OtherPets, che si traduce in:

Newtonsoft.Json.JsonSerializationException: Impossibile creare un'istanza di tipo JsonConverterLists.Mammal. Tipo è un'interfaccia o classe astratta e non può essere istanziato. 'OtherPets [0] .Lives' percorso, la linea 1, la posizione 42.

capisco cosa sta succedendo, so anche che ho potuto fissarlo con:

var actual = JsonConvert.DeserializeObject<Person>(json, new PetConverter()); 

Ma ripetendo la nota dall'alto: non riesco a modificare la chiamata DeserializeObject poiché è racchiusa all'interno di una funzione in una libreria che non posso attualmente modificare.

C'è un modo per fare lo stesso con un approccio basato sugli attributi, ad es. c'è un convertitore integrato per le liste in cui ogni voce contiene un convertitore personalizzato? O devo anche lanciare il mio convertitore separato per questo?


nota, come riprodurre:

  • Visual Studio 2013 => Fresco nuovo .NET 4.5.1 Class Library
  • Install-Package Newtonsoft.Json -Version 7.0.1
  • Install-Package nunit -Version 2.6.4

Si può solo cadere i tre blocchi di codice di cui sopra nello spazio dei nomi fresco ed eseguire i test NUnit, vedendo la seconda sicuro.

risposta

0

in base al largo @ suggerimento di AmitKumarGhosh, se si desidera aggiungere la responsabilità per le liste allo stesso convertitore, si potrebbe fare questo per fare i test passano:

public class PetConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); } 
    public override bool CanWrite { get { return false; } } 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
     { 
      return null; 
     } 

     if (reader.TokenType == JsonToken.StartArray) 
     { 
      return JArray.Load(reader) 
       .Cast<JObject>() 
       .Select(o => JObjectToMammal(o, serializer)) 
       .ToList(); 
     } 

     return JObjectToMammal(JObject.Load(reader), serializer); 
    } 

    private Mammal JObjectToMammal(JObject jsonObject, JsonSerializer serializer) 
    { 
     if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer); 
     if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer); 
     return null; 
    } 
} 
+0

Il tuo codice sembra promettente ma sta lanciando un'eccezione StackOverflow ... Sembra che jsonObject.ToObject stia chiamando ReadJson – Bidou

+0

@Bidou Hmm, strano. Per quanto posso ricordare, ho usato il codice dalla domanda * così com'è * con questa soluzione, e i test unitari hanno funzionato bene. Anche la tua riproduzione è * esattamente * uguale, o leggermente diversa? Quale versione hai usato NewtonSoft? – Jeroen

+0

Mmhmhm Sono riuscito a farlo funzionare creando una nuova classe che deriva da 'DefaultContractResolver'. Questo impedisce questo strano comportamento ... – Bidou

1

ottimizzato un po 'la classe del convertitore. la speranza è buona -

public class PetConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); } 
    public override bool CanWrite { get { return false; } } 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) return null; 

     if (reader.TokenType == JsonToken.StartArray) 
     { 
      var li = new List<Mammal>(); 
      var arr = JArray.Load(reader); 
      foreach (JObject obj in arr) 
      { 
       if (obj["Drools"] != null) 
       { 
        var k = obj.ToObject<Dog>(serializer); 
        li.Add(k); 
       } 
      } 

      return li; 
     } 


     JObject jsonObject = JObject.Load(reader); 
     if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer); 
     //if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer); 
     return null; 
    } 
} 
+0

upvoted come questo ha portato a una [risposta di lavoro] (http://stackoverflow.com/a/35310669/419956). Non posso accettarlo come è, dato che non supererà il test/ha ancora un bug o due. – Jeroen