2016-02-10 11 views
6

Mam pewne dziedzictwo, wymagające niestandardowego JsonConverter do deserializacji. Używam teraz bardzo prostego podejścia, w którym określam typ na podstawie istnienia pewnych właściwości.Jak deserializować listę elementów abstrakcyjnych bez przekazywania konwerterów do obiektu DeserializeObject?

Ważna uwaga: w moim rzeczywisty kod nie mogę dotykać połączeń DeserializeObject, to nie mogę dodać tam własne przetworników. Wiem, że jest to w pewnym stopniu problem XY i uświadom sobie, że moja odpowiedź może być taka, że ​​to, czego chcę, nie jest możliwe. O ile mogę to stwierdzić, moje pytanie nieco różni się od pytania: this question.

Oto repro mojej sytuacji:

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

I to jest w zwyczaju konwerter:

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

Działa to dobrze dla FavoritePet, ale nie tak dużo dla OtherPets bo to lista . Oto sposób, aby odtworzyć mój problem z testów 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>()); 
    } 
} 

Ten ostatni test jest czerwony, ponieważ:

Newtonsoft.Json.JsonReaderException: Błąd odczytu JObject z JsonReader. Bieżący obiekt JsonReader nie jest obiektem: StartArray. Ścieżka 'OtherPets', linia 1, pozycja 33.

Próbowałem zostały również bez niestandardowego konwertera OtherPets, co skutkuje:

Newtonsoft.Json.JsonSerializationException: Nie można utworzyć instancję typu JsonConverterLists.Mammal. Typ to interfejs lub klasa abstrakcyjna i nie można utworzyć instancji. Path 'OtherPets [0] .Lives', linia 1, pozycja 42.

rozumiem, co się dzieje, ja nawet wiem, że mógłby naprawić go:

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

ale powtarzających się Uwaga z góry: Nie mogę zmienić wywołania DeserializeObject, ponieważ jest on zawinięty wewnątrz funkcji w bibliotece, której obecnie nie mogę zmienić.

Czy można zrobić to samo z podejściem opartym na atrybutach, np. czy istnieje wbudowany konwerter dla list, gdzie każdy wpis przyjmuje niestandardowy konwerter? Czy muszę też przetasować własny, osobny konwerter?


Przypis, jak rozmnażać:

  • Visual Studio 2013 => Świeże nowy .NET 4.5.1 Class Library
  • Install-Package Newtonsoft.Json -Version 7.0.1
  • Install-Package nunit -Version 2.6.4

można po prostu upuść powyższe trzy bloki kodu w swojej świeżej nazw i uruchomić testy NUnit, widząc drugi nie.

Odpowiedz

0

oparciu off @ sugestią AmitKumarGhosh jest, jeśli chcesz dodać odpowiedzialność za list do samego konwertera, można to zrobić, testy pass:

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

Twój kod wygląda obiecująco, ale rzuca wyjątek Stackoverflow ... Wygląda na to, że jsonObject.ToObject wywołuje ReadJson – Bidou

+0

@Bidou Hmm, dziwne. O ile pamiętam, użyłem kodu z pytania * tak jak * z tym rozwiązaniem, a testy jednostkowe działały dobrze. Czy twoje repro * również * dokładnie * jest takie samo, czy subtelnie inne? W jakiej wersji NewtonSoft korzystałeś? – Jeroen

+0

Mmhmhm Udało mi się sprawić, by działało, tworząc nową klasę, która pochodzi od 'DefaultContractResolver'. Zapobiega to dziwnemu zachowaniu ... – Bidou

1

zmodyfikował trochę klasę konwertera. nadzieję, że to jest dobre -

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 jak to doprowadził do [działającej odpowiedzi] (http://stackoverflow.com/a/35310669/419956). Nie można go zaakceptować tak, jak jest, ponieważ nie przejdzie testu/nadal ma błąd lub dwa. – Jeroen

Powiązane problemy