How to call a default deserializer from a custom deserializer in Jackson

I have a problem with a custom deserializer in Jackson. I want to access the default serializer to populate the object I'm deserializing into. After filling in, I will do a few custom things, but first I want to deserialize the object with Jackson's default behavior.

This is the code that I have at the moment.

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

What I need is a way to initialize the default deserializer so that I can pre-populate my POJO before starting my custom logic.

When calling deserialization from a custom deserializer. It seems that the method is being called from the current context, regardless of how I create the serializer class. Due to annotation in my POJO. This throws an exception for obvious reasons.

I tried to initialize the BeanDeserializer but the process is extremely complicated and I could not find the right way to do this. I also tried overloading the AnnotationIntrospector no avail, believing that this could help me ignore the annotation in the DeserializerContext . Finally, I have JsonDeserializerBuilders with JsonDeserializerBuilders I may have had some success, although it took some magic to get this to get the application context from Spring. I would appreciate any thing that could lead me to a cleaner solution, such as how I can build a deserialization context without reading the JsonDeserializer annotation.

+87
java spring jackson hibernate
Aug 19 '13 at 12:02
source share
10 answers

As StaxMan already reported, you can do this by writing a BeanDeserializerModifier and registering it through the SimpleModule . The following example should work:

 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); } } 
+84
Aug 23 '13 at 14:56
source share

There are several ways to do this, but you need to do a bit more work for this. Basically, you cannot use a subclass, since the required default deserializers are built from class definitions.

So what you most likely use is to build a BeanDeserializerModifier , register it through the Module interface (use SimpleModule ). You need to define / override modifyDeserializer , and for the specific case when you want to add your own logic (where the types match), create your own deserializer, pass the default deserializer that you give. And then in the deserialize() method you can just delegate the call, take the result of Object.

Alternatively, if you really need to create and populate an object, you can do this and call the overloaded version of deserialize() , which takes a third argument; object for deserialization c.

Another way that can work (but not 100%) is to specify the Converter object ( @JsonDeserialize(converter=MyConverter.class) ). This is a new feature of Jackson 2.2. In your case, the Converter does not actually convert the type, but simplifies the modification of the object: but I do not know if this will allow you to do exactly what you want, since the default deserializer will be called first, but only your Converter .

+8
Aug 21 '13 at 19:23
source share

DeserializationContext has a readValue() method that you can use. This should work for both the default deserializer and any custom deserializers you have.

Be sure to call traverse() at the JsonNode level you want to read to get JsonParser to pass to 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; } } 
+8
May 01 '18 at 20:22
source share

I found the answer at https://stackoverflow.com/a/16665871/ ... which is much more readable than the accepted answer.

 public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode tree = jp.readTree(jp); // To call the default deserializer, simply invoke: User user = tree.get("user").traverse(jp.getCodec()).readValueAs(User.class); return user; } 

It really doesn't get any easier than that.

+6
Nov 04 '18 at 2:33
source share

If you can declare an additional User class, you can implement it using 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<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; } } 
+5
Jan 17 '18 at 10:45
source share

A simpler solution for me was to simply add another bean from ObjectMapper and use it to deserialize the object (thanks to the https://stackoverflow.com/users/1032167/varren comment) - in my case, I was interested in either deserializing its id (int) , or the whole object https://stackoverflow.com/a/377632/

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

for each object that should go through a custom deserializer, we need to configure it in the global ObjectMapper bean of the Spring boot application in my case (for example, for 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; } 
+1
Oct 07 '17 at 8:38 on
source share

I wasn’t fine using the BeanSerializerModifier , as it forced some behavioral changes to be declared in the central ObjectMapper , not the user deserializer itself, and it is actually a parallel solution for annotating an entity class using JsonSerialize . If you feel this in a similar way, you can rate my answer here: https://stackoverflow.com/a/377829/

0
Apr 04 '17 at 17:17
source share

According to what Tomasz Zaluski suggested , in cases where using the BeanDeserializerModifier undesirable, you can create a default deserializer yourself using the BeanDeserializerFactory , although some additional configuration is required. In context, this solution will look like this:

 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(); JavaType type = TypeFactory.defaultInstance().constructType(User.class); JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type)); 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; } 
0
Nov 13 '17 at 22:17
source share

You will surely fail if you try to create your own deserializer from scratch.

Instead, you need to get the (fully configured) instance of the default deserializer through a custom BeanDeserializerModifier , and then pass that instance to your deserializer class:

 public ObjectMapper getMapperWithCustomDeserializer() { ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.setDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) if (beanDesc.getBeanClass() == User.class) { return new UserEventDeserializer(defaultDeserializer); } else { return defaultDeserializer; } } }); objectMapper.registerModule(module); return objectMapper; } 

Note. This module registration replaces the @JsonDeserialize annotation, that is, the User class or User fields should no longer be annotated with this annotation.

Then the custom deserializer should be based on the DelegatingDeserializer so that the DelegatingDeserializer all methods, unless you provide an explicit implementation:

 public class UserEventDeserializer extends DelegatingDeserializer { public UserEventDeserializer(JsonDeserializer<?> delegate) { super(delegate); } @Override protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) { return new UserEventDeserializer(newDelegate); } @Override public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { User result = (User) super.deserialize(p, ctxt); // add special logic here return result; } } 
0
May 01 '19 at 15:02
source share

Here is the oneliner using ObjectMapper

 public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { OMyObject object = new ObjectMapper().readValue(p, MyObject.class); // do whatever you want return object; } 

And please: there really is no need to use any string values ​​or anything else. All necessary information is provided by JsonParser, so use it.

0
Aug 22 '19 at 7:53 on
source share



All Articles