How do I call the default deserializer from a custom deserializer in Jackson
I have a problem in my custom deserializer in Jackson. I want to access the default serializer to populate the object I am deserializing into. After the population I will do some custom things but first I want to deserialize the object with the default Jackson 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 I start my special logic.
When calling deserialize from within the custom deserializer It seems the method is called from the current context no matter how I construct the serializer class. Because of the annotation in my POJO. This causes a Stack Overflow exception for obvious reasons.
I have tried initializing a BeanDeserializer
but the process is extremely complex and I haven't managed to find the right way to do it. I have also tried overloading the AnnotationIntrospector
to no avail, thinking that it might help me ignore the annotation in the DeserializerContext
. Finally it seams I might have had some success using JsonDeserializerBuilders
although this required me to do some magic stuff to get hold of the application context from Spring. I would appreciate any thing that could lead me to a cleaner solution for example how Can I construct a deserialization context without reading the JsonDeserializer
annotation.
Solution 1:
As StaxMan already suggested you can do this by writing a BeanDeserializerModifier
and registering it via 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);
}
}
Solution 2:
The DeserializationContext
has a readValue()
method you may use. This should work for both the default deserializer and any custom deserializers you have.
Just be sure to call traverse()
on the JsonNode
level you want to read to retrieve the 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;
}
}
Solution 3:
I found an answer at https://stackoverflow.com/a/51927577/14731 which is much more readable than the accepted answer.
public User deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
User user = jp.readValueAs(User.class);
// some code
return user;
}
It really doesn't get easier than this.