Próbuję ustawić czytnik, który będzie pobierał obiekty JSON z różnych stron internetowych (pomyśl o skrobaniu informacji) i tłumaczył je na obiekty C #. Obecnie używam JSON.NET do procesu deserializacji. Problem, z którym się spotykam, polega na tym, że nie wie, jak obsługiwać właściwości na poziomie interfejsu w klasie. Więc coś z natury:
public IThingy Thing
Spowoduje błąd:
Nie można utworzyć instancji typu IThingy. Typ jest interfejsem lub klasą abstrakcyjną i nie można go utworzyć.
Jest stosunkowo ważne, aby był IThingy w przeciwieństwie do Thingy, ponieważ kod, nad którym pracuję, jest uważany za wrażliwy, a testowanie jednostkowe jest bardzo ważne. Mockowanie obiektów dla skryptów testów atomowych nie jest możliwe w przypadku pełnoprawnych obiektów, takich jak Thingy. Muszą być interfejsem.
Od jakiegoś czasu zastanawiam się nad dokumentacją JSON.NET, a pytania, które mogłem znaleźć na tej stronie, pochodzą sprzed ponad roku. Jakaś pomoc?
Ponadto, jeśli ma to znaczenie, moja aplikacja jest napisana w .NET 4.0.
Odpowiedzi:
@SamualDavis zapewnił świetne rozwiązanie w powiązanym pytaniu , które podsumuję tutaj.
Jeśli musisz deserializować strumień JSON do konkretnej klasy, która ma właściwości interfejsu, możesz dołączyć konkretne klasy jako parametry do konstruktora dla klasy! Deserializator NewtonSoft jest wystarczająco inteligentny, aby dowiedzieć się, że musi użyć tych konkretnych klas do deserializacji właściwości.
Oto przykład:
źródło
[JsonConstructor]
atrybutem.(Skopiowano z tego pytania )
W przypadkach, w których nie miałem kontroli nad przychodzącym kodem JSON (i nie mogę zapewnić, że zawiera on właściwość $ type), napisałem niestandardowy konwerter, który pozwala tylko jawnie określić konkretny typ:
Po prostu używa domyślnej implementacji serializatora z Json.Net, podczas jawnego określania konkretnego typu.
Omówienie jest dostępne w tym poście na blogu . Kod źródłowy jest poniżej:
źródło
ConcreteListTypeConverter<TInterface, TImplementation>
do obsługi członków klasy typuIList<TInterface>
.concreteTypeConverter
Jednak lepiej byłoby mieć rzeczywisty kod w pytaniu.ConcreteListTypeConverter<TInterface, TImplementation>
implementację?Dlaczego warto używać konwertera? W programie dostępna jest natywna funkcjonalność
Newtonsoft.Json
rozwiązania tego problemu:Ustaw
TypeNameHandling
wJsonSerializerSettings
toTypeNameHandling.Auto
Spowoduje to umieszczenie każdego typu w jsonie, który nie jest przechowywany jako konkretna instancja typu, ale jako interfejs lub klasa abstrakcyjna.
Upewnij się, że używasz tych samych ustawień do serializacji i deserializacji .
Przetestowałem to i działa jak urok, nawet z listami.
Wyniki wyszukiwania Wynik sieciowy z linkami do witryn
⚠️ OSTRZEŻENIE :
Używaj tego tylko dla json ze znanego i zaufanego źródła. Użytkownik snipsnipsnip poprawnie wspomniał, że jest to rzeczywiście podatność na zarabianie.
Więcej informacji można znaleźć w CA2328 i SCS0028 .
Źródło i alternatywna implementacja ręczna: Blog Code Inside
źródło
Aby włączyć deserializację wielu implementacji interfejsów, możesz użyć JsonConverter, ale nie za pomocą atrybutu:
DTOJsonConverter mapuje każdy interfejs za pomocą konkretnej implementacji:
DTOJsonConverter jest wymagany tylko w przypadku deserializatora. Proces serializacji pozostaje niezmieniony. Obiekt Json nie musi osadzać konkretnych nazw typów.
Ten post SO oferuje to samo rozwiązanie o krok dalej z ogólnym JsonConverter.
źródło
FullName
s, skoro możesz bezpośrednio porównać typy?Użyj tej klasy do mapowania typu abstrakcyjnego na typ rzeczywisty:
... i podczas deserializacji:
źródło
where TReal : TAbstract
aby upewnić się, że może rzutować na typwhere TReal : class, TAbstract, new()
.Nicholas Westby zapewnił świetne rozwiązanie w niesamowitym artykule .
Jeśli chcesz deserializować JSON do jednej z wielu możliwych klas, które implementują taki interfejs:
Możesz użyć niestandardowego konwertera JSON:
Musisz też ozdobić właściwość „Profession” atrybutem JsonConverter, aby poinformować go o używaniu niestandardowego konwertera:
Następnie możesz przesłać swoją klasę za pomocą interfejsu:
źródło
Możesz spróbować dwóch rzeczy:
Zaimplementuj model try / parse:
Lub, jeśli możesz to zrobić w modelu obiektów, zaimplementuj konkretną klasę bazową między IPerson a obiektami-liśćmi i zdeserializuj do niej.
Pierwsza może potencjalnie zawieść w czasie wykonywania, druga wymaga zmian w modelu obiektowym i homogenizuje dane wyjściowe do najniższego wspólnego mianownika.
źródło
Znalazłem to przydatne. Ty też możesz.
Przykładowe użycie
Niestandardowy konwerter tworzenia
Dokumentacja Json.NET
źródło
Dla tych, którzy mogą być ciekawi ConcreteListTypeConverter, do którego odwoływał się Oliver, oto moja próba:
źródło
CanConvert(Type objectType) { return true;}
. Wydaje się, że to hack, jak dokładnie to jest pomocne? Może się mylę, ale czy to nie jest jak mówienie mniejszemu niedoświadczonemu wojownikowi, że wygrają walkę bez względu na przeciwnika?Bez względu na to, co jest warte, w większości musiałem sobie z tym poradzić. Każdy obiekt ma metodę Deserialize (string jsonStream) . Kilka jego fragmentów:
W tym przypadku new Thingy (string) jest konstruktorem, który wywoła metodę Deserialize (string jsonStream) odpowiedniego typu konkretnego. Ten schemat będzie kontynuowany w dół i w dół, aż dojdziesz do punktów bazowych, które może obsłużyć json.NET.
Itd. itp. Ta konfiguracja pozwoliła mi podać konfiguracje json.NET, które może obsłużyć bez konieczności refaktoryzacji dużej części samej biblioteki lub używania nieporęcznych modeli try / parse, które ugrzęzłyby w całej naszej bibliotece ze względu na liczbę zaangażowanych obiektów. Oznacza to również, że mogę skutecznie obsłużyć wszelkie zmiany json na określonym obiekcie i nie muszę się martwić o wszystko, czego dotyka obiekt. W żadnym wypadku nie jest to idealne rozwiązanie, ale działa całkiem dobrze na podstawie naszych testów jednostkowych i integracyjnych.
źródło
Załóżmy, że ustawienie autofac wygląda następująco:
Następnie załóżmy, że twoja klasa wygląda tak:
Dlatego użycie resolwera w deserializacji może wyglądać następująco:
Więcej szczegółów można znaleźć pod adresem http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm
źródło
Żaden obiekt nigdy nie będzie IThingy, ponieważ wszystkie interfejsy są z definicji abstrakcyjne.
Przedmiotem masz że po raz pierwszy w odcinkach było jakiejś betonowej typu realizacji abstrakcyjny interfejs. Musisz mieć tę samą konkretną klasę ożywiającą zserializowane dane.
Powstały obiekt będzie wtedy pewnego rodzaju, który implementuje abstrakcyjny interfejs, którego szukasz.
Z dokumentacji wynika, że możesz użyć
podczas deserializacji, aby poinformować JSON.NET o konkretnym typie.
źródło
_type
właściwość sygnalizująca konkretny typ do użycia.Moje rozwiązanie tego, które mi się podoba, bo jest ładnie ogólne, jest następujące:
}
Można oczywiście i trywialnie przekonwertować go na jeszcze bardziej ogólny konwerter, dodając konstruktor, który pobierał argument typu Dictionary <Type, Type>, za pomocą którego można utworzyć wystąpienie zmiennej wystąpienia konwersji.
źródło
Kilka lat później miałem podobny problem. W moim przypadku były mocno zagnieżdżone interfejsy i preferencja generowania konkretnych klas w czasie wykonywania, tak aby działała z klasą generyczną.
Zdecydowałem się utworzyć klasę proxy w czasie wykonywania, która otacza obiekt zwracany przez Newtonsoft.
Zaletą tego podejścia jest to, że nie wymaga ono konkretnej implementacji klasy i może automatycznie obsługiwać dowolną głębokość zagnieżdżonych interfejsów. Więcej na ten temat można przeczytać na moim blogu .
Stosowanie:
źródło
PopulateObject
na proxy generowanym przez Impromptu Interface. Niestety zrezygnowałem z Duck Typing - po prostu łatwiej było stworzyć niestandardowy Serializer kontraktu Json, który wykorzystywał odbicie, aby znaleźć istniejącą implementację żądanego interfejsu i użyć tego.Użyj tego JsonKnownTypes , jest to bardzo podobny sposób użycia, po prostu dodaj dyskryminator do json:
Teraz podczas serializacji obiekt w json zostanie dodany
"$type"
z"myClass"
wartością i będzie używany do deserializacjiJson:
źródło
W moim rozwiązaniu dodano elementy interfejsu w konstruktorze.
źródło