Como faço para chamar o desserializador padrão de um desserializador personalizado em Jackson

Eu tenho um problema no meu deserializador personalizado em Jackson. Eu quero acessar o serializador padrão para preencher o object que eu estou desserializando. Depois da população, farei algumas coisas personalizadas, mas primeiro quero desserializar o object com o comportamento padrão de jackson.

Este é o código que tenho no momento.

public class UserEventDeserializer extends StdDeserializer { 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; } } 

O que eu preciso é uma maneira de inicializar o desserializador padrão para que eu possa pré-preencher meu POJO antes de iniciar minha lógica especial.

Ao chamar deserializar a partir do desserializador customizado, o método é chamado a partir do contexto atual, independentemente de como eu construo a class do serializador. Por causa da anotação no meu POJO. Isso causa uma exceção de estouro de pilha por motivos óbvios. Eu tentei inicializar um beandeserializer mas o processo é extremamente complexo e eu não consegui encontrar o caminho certo para fazê-lo. Eu também tentei sobrecarregar o introspector de anotação sem sucesso, pensando que isso poderia me ajudar a ignorar a anotação no DeserializerContext. Finalmente, parece que eu tive algum sucesso usando JsonDeserializerBuilders, embora isso exigisse que eu fizesse algumas coisas mágicas para obter o contexto do aplicativo a partir da primavera. Eu apreciaria qualquer coisa que poderia me levar a uma solução mais limpa, por exemplo, como eu poderia construir um contexto de desserialização sem ler a anotação JsonDeserializer.

Como o StaxMan já sugeriu, você pode fazer isso escrevendo um BeanDeserializerModifier e registrando-o via SimpleModule . O exemplo a seguir deve funcionar:

 public class UserEventDeserializer extends StdDeserializer implements ResolvableDeserializer { private static final long serialVersionUID = 7923585097068641765L; private final JsonDeserializer< ?> defaultDeserializer; public UserEventDeserializer(JsonDeserializer< ?> defaultDeserializer) { super(User.class); this.defaultDeserializer = defaultDeserializer; } @Override public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt); // Special logic return deserializedUser; } // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer // otherwise deserializing throws JsonMappingException?? @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); } public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException { SimpleModule module = new SimpleModule(); module.setDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer< ?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer< ?> deserializer) { if (beanDesc.getBeanClass() == User.class) return new UserEventDeserializer(deserializer); return deserializer; } }); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(module); User user = mapper.readValue(new File("test.json"), User.class); } } 

Existem algumas maneiras de fazer isso, mas para fazer isso direito envolve pouco mais trabalho. Basicamente, você não pode usar subclasss, uma vez que os desserializadores padrão de informações necessários são criados a partir de definições de class.

Então, o que você pode usar mais provavelmente é construir um BeanDeserializerModifier , registre-o via interface Module (use SimpleModule ). Você precisa definir / replace modifyDeserializer , e para o caso específico em que você deseja adicionar sua própria lógica (onde o tipo corresponde), construa seu próprio desserializador, passe o desserializador padrão que você recebe. E, em seguida, no método deserialize() você pode apenas delegar a chamada, pegue o resultado Object.

Como alternativa, se você realmente precisa criar e preencher o object, pode fazê-lo e chamar a versão sobrecarregada de deserialize() que recebe o terceiro argumento; object para desserializar em.

Outra maneira que pode funcionar (mas não 100% de certeza) seria especificar o object Converter ( @JsonDeserialize(converter=MyConverter.class) ). Este é um novo recurso do Jackson 2.2. No seu caso, o Conversor na verdade não converteria o tipo, mas simplificaria a modificação do object: mas eu não sei se isso permitiria fazer exatamente o que você quer, já que o desserializador padrão seria chamado primeiro, e só então o seu Converter .

Se for possível declarar uma class de usuário extra, você poderá implementá-la usando apenas annotations

 // your class @JsonDeserialize(using = UserEventDeserializer.class) public class User { ... } // extra user class // reset deserializer attribute to default @JsonDeserialize public class UserPOJO extends User { } public class UserEventDeserializer extends StdDeserializer { ... @Override public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // specify UserPOJO.class to invoke default deserializer User deserializedUser = jp.ReadValueAs(UserPOJO.class); return deserializedUser; // or if you need to walk the JSON tree ObjectMapper mapper = (ObjectMapper) jp.getCodec(); JsonNode node = oc.readTree(jp); // specify UserPOJO.class to invoke default deserializer User deserializedUser = mapper.treeToValue(node, UserPOJO.class); return deserializedUser; } } 

O DeserializationContext tem um método readValue() que você pode usar. Isso deve funcionar tanto para o desserializador padrão quanto para os desserializadores personalizados que você possui.

Apenas certifique-se de chamar traverse() no nível JsonNode você deseja ler para recuperar o JsonParser para passar para readValue() .

 public class FooDeserializer extends StdDeserializer { private static final long serialVersionUID = 1L; public FooDeserializer() { this(null); } public FooDeserializer(Class t) { super(t); } @Override public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = jp.getCodec().readTree(jp); FooBean foo = new FooBean(); foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class)); return foo; } } 

Uma solução mais simples para mim foi simplesmente adicionar outro bean do ObjectMapper e usá-lo para desserializar o object (graças ao https://stackoverflow.com/users/1032167/varren comment) – no meu caso eu estava interessado em desserializar para o seu id (um int) ou o object inteiro https://stackoverflow.com/a/46618193/986160

 import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.springframework.context.annotation.Bean; import java.io.IOException; public class IdWrapperDeserializer extends StdDeserializer { private Class clazz; public IdWrapperDeserializer(Class clazz) { super(clazz); this.clazz = clazz; } @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); return mapper; } @Override public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { String json = jp.readValueAsTree().toString(); // do your custom deserialization here using json // and decide when to use default deserialization using local objectMapper: T obj = objectMapper().readValue(json, clazz); return obj; } } 

Para cada entidade que precisa passar pelo deserializador personalizado, precisamos configurá-lo no bean global ObjectMapper do Spring Boot App no ​​meu caso (por exemplo, para Category ):

 @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); SimpleModule testModule = new SimpleModule("MyModule") .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class)) mapper.registerModule(testModule); return mapper; } 

Eu não estava bem com o uso do BeanSerializerModifier pois ele força a declaração de algumas mudanças comportamentais no ObjectMapper central, e não no próprio desserializador customizado, e na verdade é uma solução paralela para anotar a class de entidade com o JsonSerialize . Se você sentir da mesma maneira, talvez aprecie minha resposta aqui: https://stackoverflow.com/a/43213463/653539

Ao longo das linhas do que Tomáš Záluský sugeriu , nos casos em que o uso de BeanDeserializerModifier é indesejável, você pode construir um desserializador padrão usando BeanDeserializerFactory , embora haja alguma configuração extra necessária. No contexto, esta solução seria assim:

 public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ObjectCodec oc = jp.getCodec(); JsonNode node = oc.readTree(jp); User deserializedUser = null; DeserializationConfig config = ctxt.getConfig(); JsonDeserializer defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, User.class, config.introspect(User.class)); if (defaultDeserializer instanceof ResolvableDeserializer) { ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); } JsonParser treeParser = oc.treeAsTokens(node); config.initialize(treeParser); if (treeParser.getCurrentToken() == null) { treeParser.nextToken(); } deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context); return deserializedUser; }