serialize example deserializer custom java spring hibernate jackson

java - example - ¿Cómo llamo al deserializador predeterminado de un deserializador personalizado en Jackson



objectmapper java (7)

Tengo un problema en mi deserializador personalizado en Jackson. Quiero acceder al serializador predeterminado para rellenar el objeto en el que estoy deserializando. Después de la población, haré algunas cosas personalizadas, pero primero quiero deserializar el objeto con el comportamiento predeterminado de jackson.

Este es el código que tengo en este momento.

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

Lo que necesito es una forma de inicializar el deserializador predeterminado para que pueda llenar previamente mi POJO antes de comenzar mi lógica especial.

Al llamar a deserialize desde el deserializador personalizado, ve que se llama al método desde el contexto actual sin importar cómo construyo la clase de serializador. Debido a la anotación en mi POJO. Esto causa una excepción de desbordamiento de pila por razones obvias. He intentado inicializar un beandeserializer pero el proceso es extremadamente complejo y no he logrado encontrar la forma correcta de hacerlo. También intenté sobrecargar el introspector de anotación en vano, pensando que podría ayudarme a ignorar la anotación en DeserializerContext. Finalmente, creo que podría haber tenido algún éxito usando JsonDeserializerBuilders aunque esto requirió que hiciera algunas cosas mágicas para obtener el contexto de la aplicación desde la primavera. Apreciaría cualquier cosa que pueda llevarme a una solución más limpia, por ejemplo, cómo puedo construir un contexto de deserialización sin leer la anotación JsonDeserializer.


Como StaxMan ya sugirió, puede hacerlo escribiendo un BeanDeserializerModifier y registrándolo a través de SimpleModule . El siguiente ejemplo debería funcionar:

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


El DeserializationContext tiene un método readValue() que puede usar. Esto debería funcionar tanto para el deserializador predeterminado como para cualquier deserializador personalizado que tenga.

Solo asegúrese de llamar a traverse() en el nivel JsonNode que desea leer para recuperar JsonParser y pasarlo a readValue() .

public class FooDeserializer extends StdDeserializer<FooBean> { private static final long serialVersionUID = 1L; public FooDeserializer() { this(null); } public FooDeserializer(Class<FooBean> 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; } }


En la línea de lo que Tomáš Záluský ha sugerido , en los casos en que utilizar BeanDeserializerModifier es deseable, puede construir un deserializador predeterminado utilizando BeanDeserializerFactory , aunque es necesario realizar alguna configuración adicional. En contexto, esta solución se vería así:

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


Hay dos formas de hacer esto, pero hacerlo bien implica un poco más de trabajo. Básicamente no se puede usar la subclasificación, ya que la información predeterminada que necesitan los deserializadores se basa en las definiciones de clase.

Entonces, lo que probablemente pueda usar es construir un BeanDeserializerModifier , registrarlo a través de la interfaz del Module (use SimpleModule ). Necesita definir / anular modifyDeserializer , y para el caso específico donde desea agregar su propia lógica (donde el tipo coincide), construya su propio deserializador, pase el deserializador predeterminado que se le otorga. Y luego, en el método deserialize() puedes simplemente delegar la llamada, tomar el Objeto resultante.

Alternativamente, si realmente debe crear y poblar el objeto, puede hacerlo y llamar a la versión sobrecargada de deserialize() que toma un tercer argumento; objeto para deserializar en.

Otra forma que podría funcionar (pero no 100% seguro) sería especificar el objeto Converter ( @JsonDeserialize(converter=MyConverter.class) ). Esta es una nueva característica de Jackson 2.2. En su caso, el convertidor en realidad no convertiría el tipo, sino que simplificaría la modificación del objeto: pero no sé si eso le permitiría hacer exactamente lo que desea, ya que primero se llamaría al deserializador predeterminado y solo a su Converter .


No estaba bien con el uso de BeanSerializerModifier ya que obliga a declarar algunos cambios de comportamiento en ObjectMapper central en lugar de hacerlo en el propio deserializador personalizado y, de hecho, es una solución paralela para anotar la clase de entidad con JsonSerialize . Si lo sientes de la misma manera, puedes apreciar mi respuesta aquí: https://.com/a/43213463/653539


Si es posible declarar una clase de usuario adicional, entonces puede implementarla simplemente usando anotaciones

// 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<User> { ... @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; } }


Una solución más simple para mí fue simplemente agregar otro bean de ObjectMapper y usarlo para deserializar el objeto (gracias a https://.com/users/1032167/varren comment) - en mi caso me interesaba deserializar su id (an int) o todo el objeto https://.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<T> extends StdDeserializer<T> { private Class<T> clazz; public IdWrapperDeserializer(Class<T> 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 entidad que necesita pasar por un deserializador personalizado, debemos configurarlo en el bean ObjectMapper global de la aplicación Spring Boot en mi caso (por ejemplo, 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; }