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.
Twój kod wygląda obiecująco, ale rzuca wyjątek Stackoverflow ... Wygląda na to, że jsonObject.ToObject wywołuje ReadJson – Bidou
@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
Mmhmhm Udało mi się sprawić, by działało, tworząc nową klasę, która pochodzi od 'DefaultContractResolver'. Zapobiega to dziwnemu zachowaniu ... – Bidou