Mam problem z moim niestandardowym deserializatorem w Jackson. Chcę uzyskać dostęp do domyślnego serializatora, aby wypełnić obiekt, do którego deserializuję. Po populacji zrobię kilka niestandardowych rzeczy, ale najpierw chcę deserializować obiekt z domyślnym zachowaniem Jacksona.
To jest kod, który mam w tej chwili.
public class UserEventDeserializer extends StdDeserializer<User> {
private static final long serialVersionUID = 7923585097068641765L;
public UserEventDeserializer() {
super(User.class);
}
@Override
@Transactional
public User deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
User deserializedUser = null;
deserializedUser = super.deserialize(jp, ctxt, new User());
// The previous line generates an exception java.lang.UnsupportedOperationException
// Because there is no implementation of the deserializer.
// I want a way to access the default spring deserializer for my User class.
// How can I do that?
//Special logic
return deserializedUser;
}
}
To, czego potrzebuję, to sposób na zainicjowanie domyślnego deserializatora, aby móc wstępnie wypełnić moje POJO przed rozpoczęciem mojej specjalnej logiki.
Podczas wywoływania deserializacji z poziomu niestandardowego deserializatora Wygląda na to, że metoda jest wywoływana z bieżącego kontekstu, niezależnie od tego, jak skonstruuję klasę serializatora. Z powodu adnotacji w moim POJO. Powoduje to wyjątek przepełnienia stosu z oczywistych powodów.
Próbowałem zainicjować plik, BeanDeserializer
ale proces jest niezwykle złożony i nie udało mi się znaleźć właściwego sposobu, aby to zrobić. Próbowałem również przeciążać AnnotationIntrospector
plik bez skutku, myśląc, że może mi to pomóc zignorować adnotację w DeserializerContext
. W końcu wydaje się, że mogłem odnieść sukces, JsonDeserializerBuilders
chociaż wymagało to ode mnie magicznych rzeczy, aby poznać kontekst aplikacji ze Springa. Byłbym wdzięczny za każdą rzecz, która mogłaby doprowadzić mnie do bardziej przejrzystego rozwiązania, na przykład jak mogę skonstruować kontekst deserializacji bez czytania JsonDeserializer
adnotacji.
DeserializationContext
nie jest czymś, co powinieneś stworzyć lub zmienić; będzie dostarczony przezObjectMapper
.AnnotationIntrospector
podobnie nie pomoże w uzyskaniu dostępu.Odpowiedzi:
Jak już zasugerował StaxMan, możesz to zrobić, pisząc
BeanDeserializerModifier
i rejestrując go przezSimpleModule
. Poniższy przykład powinien działać:źródło
JsonSerializer
? Mam kilka serializatorów, ale mają wspólny kod, więc chcę go wygenerować. Próbuję bezpośrednio wywołać serializator, ale wynik nie jest rozpakowany w wyniku JSON (każde wywołanie serializatora tworzy nowy obiekt)BeanSerializerModifier
,ResolvableSerializer
iContextualSerializer
są dopasowane do wykorzystania interfejsy dla serializacji.readTree()
ale odpowiedź nie. Jaka jest przewaga tego podejścia w porównaniu z podejściem opublikowanym przez Dereka Cochrana ? Czy jest sposób, aby to zadziałałoreadTree()
?Znalazłem odpowiedź w ans, która jest znacznie bardziej czytelna niż zaakceptowana odpowiedź.
To naprawdę nie jest łatwiejsze niż to.
źródło
StackOverflowError
, ponieważ Jackson ponownie użyje tego samego serializatora dlaUser
...DeserializationContext
MareadValue()
metodę można wykorzystać. Powinno to działać zarówno w przypadku domyślnego deserializatora, jak i wszystkich posiadanych niestandardowych deserializatorów.Tylko pamiętaj, aby zadzwonić
traverse()
naJsonNode
poziomie, który chcesz przeczytać, aby odzyskaćJsonParser
przejść doreadValue()
.źródło
Jest na to kilka sposobów, ale zrobienie tego dobrze wymaga trochę więcej pracy. Zasadniczo nie można używać podklas, ponieważ informacje, których potrzebują domyślne deserializatory, są budowane na podstawie definicji klas.
Więc to, co najprawdopodobniej możesz użyć, to skonstruować
BeanDeserializerModifier
, zarejestrować to przezModule
interfejs (użyjSimpleModule
). Musisz zdefiniować / przesłonićmodifyDeserializer
, a dla konkretnego przypadku, w którym chcesz dodać własną logikę (gdzie typ jest zgodny), skonstruować własny deserializator, przekazać domyślny deserializator, który otrzymałeś. A następnie wdeserialize()
metodzie możesz po prostu delegować wywołanie, przyjmować wynik Object.Alternatywnie, jeśli musisz faktycznie utworzyć i zapełnić obiekt, możesz to zrobić i wywołać przeciążoną wersję tego,
deserialize()
która przyjmuje trzeci argument; obiekt do deserializacji.Innym sposobem, który może zadziałać (ale nie na 100%), byłoby określenie
Converter
obiektu (@JsonDeserialize(converter=MyConverter.class)
). To jest nowa funkcja Jacksona 2.2. W twoim przypadku Converter faktycznie nie konwertuje typu, ale upraszcza modyfikację obiektu: ale nie wiem, czy pozwoliłoby ci to zrobić dokładnie to, co chcesz, ponieważ domyślny deserializator zostałby wywołany jako pierwszy, a dopiero potem twójConverter
.źródło
BeanDeserializerModifier
to program obsługi wywołań zwrotnych, który na to pozwala.Jeśli możesz zadeklarować dodatkową klasę użytkownika, możesz ją zaimplementować po prostu za pomocą adnotacji
źródło
Zgodnie z tym, co zasugerował Tomáš Záluský , w przypadkach, gdy używanie
BeanDeserializerModifier
jest niepożądane, możesz samodzielnie skonstruować domyślny deserializatorBeanDeserializerFactory
, chociaż jest wymagana dodatkowa konfiguracja. W kontekście takie rozwiązanie wyglądałoby tak:źródło
Oto oneliner używający ObjectMapper
I proszę: naprawdę nie ma potrzeby używania żadnej wartości String ani czegoś innego. Wszystkie potrzebne informacje są dostarczane przez JsonParser, więc używaj go.
źródło
Nie byłem w porządku z używaniem,
BeanSerializerModifier
ponieważ wymusza to deklarowanie pewnych zmian behawioralnych w centralnym,ObjectMapper
a nie w niestandardowym deserializatorze i w rzeczywistości jest to równoległe rozwiązanie do dodawania adnotacji do klasy encji za pomocąJsonSerialize
. Jeśli czujesz to podobnie, możesz docenić moją odpowiedź tutaj: https://stackoverflow.com/a/43213463/653539źródło
Prostszym rozwiązaniem było dla mnie po prostu dodanie kolejnego beana
ObjectMapper
i użycie go do deserializacji obiektu (dzięki komentarzowi https://stackoverflow.com/users/1032167/varren ) - w moim przypadku byłem zainteresowany albo deserializacją do jego id (int) lub cały obiekt https://stackoverflow.com/a/46618193/986160dla każdego podmiotu, który musi przejść przez niestandardowy deserializer, musimy skonfigurować go w globalnym
ObjectMapper
beanie aplikacji Spring Boot w moim przypadku (np. dlaCategory
):źródło
Jeśli spróbujesz utworzyć niestandardowy deserializator od podstaw, poniesiesz porażkę.
Zamiast tego musisz uzyskać (w pełni skonfigurowane) domyślne wystąpienie deserializatora za pośrednictwem niestandardowego
BeanDeserializerModifier
, a następnie przekazać to wystąpienie do niestandardowej klasy deserializatora:Uwaga: Ta rejestracja modułu zastępuje
@JsonDeserialize
adnotację, tj.User
Klasa lubUser
pola nie powinny już być adnotowane tą adnotacją.Niestandardowy deserializator powinien następnie być oparty na
DelegatingDeserializer
tak, aby delegować wszystkie metody, chyba że podasz jawną implementację:źródło
Używanie
BeanDeserializerModifier
działa dobrze, ale jeśli musisz go użyćJsonDeserialize
, jest na to sposóbAnnotationIntrospector
:Teraz skopiowany program mapujący będzie teraz ignorował Twój niestandardowy deserializer (MyDeserializer.class) i użyje domyślnej implementacji. Możesz go użyć wewnątrz
deserialize
metody niestandardowego deserializatora, aby uniknąć rekursji, ustawiając skopiowany element mapper jako statyczny lub łącząc go, jeśli używasz Spring.źródło