Related
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.
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);
}
}
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;
}
}
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.
If it is possible for you to declare extra User class then you can implement it just 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;
}
}
There are couple of ways to do this, but to do it right involves bit more work. Basically you can not use sub-classing, since information default deserializers need is built from class definitions.
So what you can most likely use is to construct a BeanDeserializerModifier, register that via Module interface (use SimpleModule). You need to define/override modifyDeserializer, and for the specific case where you want to add your own logic (where type matches), construct your own deserializer, pass the default deserializer you are given.
And then in deserialize() method you can just delegate call, take the result Object.
Alternatively, if you must actually create and populate the object, you can do so and call overloaded version of deserialize() that takes third argument; object to deserialize into.
Another way that might work (but not 100% sure) would be to specify Converter object (#JsonDeserialize(converter=MyConverter.class)). This is a new Jackson 2.2 feature.
In your case, Converter would not actually convert type, but simplify modify the object: but I don't know if that would let you do exactly what you want, since the default deserializer would be called first, and only then your Converter.
Along the lines of what Tomáš Záluský has suggested, in cases where using BeanDeserializerModifier is undesirable you can construct a default deserializer yourself using BeanDeserializerFactory, although there is some extra setup necessary. In context, this solution would look like so:
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;
}
You are bound to fail if you try to create your custom deserializer from scratch.
Instead, you need to get hold of the (fully configured) default deserializer instance through a custom BeanDeserializerModifier, and then pass this instance to your custom 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, i.e. the User class or User fields should no longer be annotated with this annotation.
The custom deserializer should then be based on a DelegatingDeserializer so that all methods delegate, 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;
}
}
I was not ok with using BeanSerializerModifier since it forces to declare some behavioral changes in central ObjectMapper rather than in custom deserializer itself and in fact it is parallel solution to annotating entity class with JsonSerialize. If you feel it the similar way, you might appreciate my answer here: https://stackoverflow.com/a/43213463/653539
Using BeanDeserializerModifier works well, but if you need to use JsonDeserialize there is a way to do it with AnnotationIntrospector
like this:
ObjectMapper originalMapper = new ObjectMapper();
ObjectMapper copy = originalMapper.copy();//to keep original configuration
copy.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Object findDeserializer(Annotated a) {
Object deserializer = super.findDeserializer(a);
if (deserializer == null) {
return null;
}
if (deserializer.equals(MyDeserializer.class)) {
return null;
}
return deserializer;
}
});
Now copied mapper will now ignore your custom deserializer (MyDeserializer.class) and use default implementation. You can use it inside deserialize method of your custom deserializer to avoid recursion by making copied mapper static or wire it if using Spring.
A simpler solution for me was to just add another bean of ObjectMapper and use that to deserialize the object (thanks to https://stackoverflow.com/users/1032167/varren comment) - in my case I was interested to either deserialize to its id (an int) or the whole object 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<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 entity that needs to be going through custom deserializer we need to configure it in the global ObjectMapper bean of the Spring Boot App in my case (e.g 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;
}
Here is a short solution using default ObjectMapper
private static final ObjectMapper MAPPER = new ObjectMapper(); // use default mapper / mapper without customization
public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyObject object = MAPPER.readValue(p, MyObject.class);
// do whatever you want
return object;
}
And please: There is really no need to use any String value or something else. All needed information are given by JsonParser, so use it.
I'm working with a 3rd party JSON API, it returns data like this:
{details: {...}, ...}
I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :
#JsonProperty("details")
public Details getDetails(){...}
and Details is another class.
Everything is fine until I found that API may return data like this:
{details: false, ...}
If details is empty, it returns false!!! And jackson gave me this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])
So, how to handle this kind of JSON string? I only need this field to set to null if empty.
The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:
I created this example POJO, with a static factory method, annotated so that Jackson uses it:
public class Details {
public String name; // example property
#JsonCreator
public static Details factory(Map<String,Object> props) {
if (props.get("details") instanceof Boolean) return null;
Details details = new Details();
Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
details.name = (String)detailsProps.get("name");
return details;
}
}
test method:
public static void main(String[] args)
{
String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
String emptyDetailsJson = "{\"details\": false} ";
ObjectMapper mapper = new ObjectMapper();
try {
Details details = mapper.readValue(fullDetailsJson, Details.class);
System.out.println(details.name);
details = mapper.readValue(emptyDetailsJson, Details.class);
System.out.println(details);
} catch (Exception e) {
e.printStackTrace();
}
}
result is as expected:
My Details
null
Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:
public class CustomDeserializer extends JsonDeserializer<Details>{
#Override
public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
//if object use default deserializer else return null
}
}
You'll also have to write an ObjectMapperProvider to register your deserializer like so:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
private ObjectMapper mapper;
public ObjectMapperProvider(){
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Details.class, new CustomDeserializer());
mapper.registerModule(sm);
}
public ObjectMapper getContext(Class<?> arg0){
return mapper;
}
}
Imagine the following scenario:
class <T> Foo<T> {
....
}
class Bar {
Foo<Something> foo;
}
I want to write a custom Jackson deserializer for Foo. In order to do that (for example, in order to deserialize Bar class that has Foo<Something> property), I need to know the concrete type of Foo<T>, used in Bar, at deserialization time (e.g. I need to know that T is Something in that particluar case).
How does one write such a deserializer? It should be possible to do it, since Jackson does it with typed collections and maps.
Clarifications:
It seems there are 2 parts to solution of the problem:
1) Obtain declared type of property foo inside Bar and use that to deserialize Foo<Somehting>
2) Find out at deserialization time that we are deserializing property foo inside class Bar in order to successfully complete step 1)
How does one complete 1 and 2 ?
You can implement a custom JsonDeserializer for your generic type which also implements ContextualDeserializer.
For example, suppose we have the following simple wrapper type that contains a generic value:
public static class Wrapper<T> {
public T value;
}
We now want to deserialize JSON that looks like this:
{
"name": "Alice",
"age": 37
}
into an instance of a class that looks like this:
public static class Person {
public Wrapper<String> name;
public Wrapper<Integer> age;
}
Implementing ContextualDeserializer allows us to create a specific deserializer for each field in the Person class, based on the generic type parameters of the field. This allows us to deserialize the name as a string, and the age as an integer.
The complete deserializer looks like this:
public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType valueType;
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
WrapperDeserializer deserializer = new WrapperDeserializer();
deserializer.valueType = valueType;
return deserializer;
}
#Override
public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.value = ctxt.readValue(parser, valueType);
return wrapper;
}
}
It is best to look at createContextual here first, as this will be called first by Jackson. We read the type of the field out of the BeanProperty (e.g. Wrapper<String>) and then extract the first generic type parameter (e.g. String). We then create a new deserializer and store the inner type as the valueType.
Once deserialize is called on this newly created deserializer, we can simply ask Jackson to deserialize the value as the inner type rather than as the whole wrapper type, and return a new Wrapper containing the deserialized value.
In order to register this custom deserializer, we then need to create a module that contains it, and register that module:
SimpleModule module = new SimpleModule()
.addDeserializer(Wrapper.class, new WrapperDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
If we then try to deserialize the example JSON from above, we can see that it works as expected:
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person.name.value); // prints Alice
System.out.println(person.age.value); // prints 37
There are some more details about how contextual deserializers work in the Jackson documentation.
If the target itself is a generic type then property will be null, for that you'll need to get the valueTtype from the DeserializationContext:
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
if (property == null) { // context is generic
JMapToListParser parser = new JMapToListParser();
parser.valueType = ctxt.getContextualType().containedType(0);
return parser;
} else { // property is generic
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
JMapToListParser parser = new JMapToListParser();
parser.valueType = valueType;
return parser;
}
}
This is how you can access/resolve {targetClass} for a Custom Jackson Deserializer. Of course you need to implement ContextualDeserializer interface for this.
public class WPCustomEntityDeserializer extends JsonDeserializer<Object>
implements ContextualDeserializer {
private Class<?> targetClass;
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
//Your code here to customize deserialization
// You can access {target class} as targetClass (defined class field here)
//This should build some {deserializedClasObject}
return deserializedClasObject;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property){
//Find here the targetClass to be deserialized
String targetClassName=ctxt.getContextualType().toCanonical();
try {
targetClass = Class.forName(targetClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return this;
}
}
For my use case, none of the above solutions worked, so I had to write a custom module. You can find my implementation on GitHub.
I wanted to write a deserializer that automatically removes blank Strings from Lists.
I need to create a general deserializer; in other words I don't know what the deserialised target class will be.
I have seen examples on the internet where by they create a deserializer such as JsonDeserializer<Customer> and then return a new Customer(...) at the end. The problem is that I don't know what the return class will be.
I imagine I will need to use reflection to create an instance of the class and populate the field. How can I do it from the deserialize method?
public class JsonApiDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
//Need to parse the JSON and return a new instance here
}
}
After some tests, I find #jax 's answer has a problem.
As #Staxman pointed out, createContextual() is called during construction of Deserializer, not in every process of deserialization. And the deserializer returned by createContextual will be cached by the Jackson library. So if your deserializer is used with more than 1 type(such as sub types of a common parent), it will throw out type mismatch exception, cause the targetClass property will be the last type cached by the Jackson library.
The correct solution should be:
public class JsonApiDeserializer extends JsonDeserializer<Object> implements
ContextualDeserializer {
private Class<?> targetClass;
public JsonApiDeserializer() {
}
public JsonApiDeserializer(Class<?> targetClass) {
this.targetClass = targetClass;
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Object clazz = targetClass.newInstance();
//Now I have an instance of the annotated class I can populate the fields via reflection
return clazz;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
//gets the class type of the annotated class
targetClass = ctxt.getContextualType().getRawClass();
//this new JsonApiDeserializer will be cached
return new JsonApiDeserializer(targetClass);
}
}
Essentially, there are only 2 cases you need to cater for, Object and Object[], for which you can always deserialize to:
A Map
An array of Map
Something like this should work:
public class JsonApiDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = jp.getText();
if (text.startsWith("{"))
return new ObjectMapper().readValue(text, Map.class);
return new ObjectMapper().readValue(text, Map[].class);
}
}
Disclaimer: Uncompiled and untested
I got it working using ContextualDeserializer
public class JsonApiDeserializer extends JsonDeserializer<Object> implements
ContextualDeserializer {
private Class<?> targetClass;
#SneakyThrows
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Object clazz = targetClass.newInstance();
//Now I have an instance of the annotated class I can populate the fields via reflection
return clazz;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
//gets the class type of the annotated class
targetClass = ctxt.getContextualType().getRawClass();
return this;
}
}
I am still a little unsure of why this works as I already have a DeserializationContext ctxt in the original deserialize method but it returns null when I do ctxt.getContextualType().
Can someone explain?
If you know the message structure in advance, you can use this tool to easily generate POJOs from a given JSON string.
However, if your message format changes during runtime, and there is no other information for you to determine the type (for example, header information) you can deserialize into a Map and process the fields manually.
For example, with Jackson:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> userData = mapper.readValue(jsonData, Map.class);
I am not sure I completely got your question right but what you can do is to inspect the properties of the json inside the deserialiser doing something like:
ObjectMapper objectMapper = (ObjectMapper) jp.getCodec();
ObjectNode node = objectMapper.readTree(jp);
and then node.has("propertyName") so that you can create, setup and return your object and leave to the client of the deserialiser the responsibility of the cast.
you say " in other words I don't know what the deserialised target class will be. "
so I don't get if you can at least infer that, more info would be helpful
If you know which classes can be deserialized in compile-time, but need to dynamically choose the right one in runtime depending on JSON contents I can suggest the following.
Add some classifier field into the JSON. This field will help your code know how to deal with the following data. As far as I can see, you already have the "type" field so that can be used.
Introduce a factory that will instantiate the specific classes depending on the input from JSON. For example, it may have the method like Object create(string typeFromJson, Map data). Such factory may populate the newly-created object with the data as well.
If this is not the case and you don't know your required interfaces already, you are in trouble. It can be somewhat be worked around in C# with the use of dynamic keyword, but Java doesn't have such a feature yet.
Also, AFAIK, there is a way in Jackson to specify classes that need to be automatically deserialized and injected into #Post method calls in your REST resource class.
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.
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);
}
}
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;
}
}
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.
If it is possible for you to declare extra User class then you can implement it just 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;
}
}
There are couple of ways to do this, but to do it right involves bit more work. Basically you can not use sub-classing, since information default deserializers need is built from class definitions.
So what you can most likely use is to construct a BeanDeserializerModifier, register that via Module interface (use SimpleModule). You need to define/override modifyDeserializer, and for the specific case where you want to add your own logic (where type matches), construct your own deserializer, pass the default deserializer you are given.
And then in deserialize() method you can just delegate call, take the result Object.
Alternatively, if you must actually create and populate the object, you can do so and call overloaded version of deserialize() that takes third argument; object to deserialize into.
Another way that might work (but not 100% sure) would be to specify Converter object (#JsonDeserialize(converter=MyConverter.class)). This is a new Jackson 2.2 feature.
In your case, Converter would not actually convert type, but simplify modify the object: but I don't know if that would let you do exactly what you want, since the default deserializer would be called first, and only then your Converter.
Along the lines of what Tomáš Záluský has suggested, in cases where using BeanDeserializerModifier is undesirable you can construct a default deserializer yourself using BeanDeserializerFactory, although there is some extra setup necessary. In context, this solution would look like so:
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;
}
You are bound to fail if you try to create your custom deserializer from scratch.
Instead, you need to get hold of the (fully configured) default deserializer instance through a custom BeanDeserializerModifier, and then pass this instance to your custom 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, i.e. the User class or User fields should no longer be annotated with this annotation.
The custom deserializer should then be based on a DelegatingDeserializer so that all methods delegate, 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;
}
}
I was not ok with using BeanSerializerModifier since it forces to declare some behavioral changes in central ObjectMapper rather than in custom deserializer itself and in fact it is parallel solution to annotating entity class with JsonSerialize. If you feel it the similar way, you might appreciate my answer here: https://stackoverflow.com/a/43213463/653539
Using BeanDeserializerModifier works well, but if you need to use JsonDeserialize there is a way to do it with AnnotationIntrospector
like this:
ObjectMapper originalMapper = new ObjectMapper();
ObjectMapper copy = originalMapper.copy();//to keep original configuration
copy.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Object findDeserializer(Annotated a) {
Object deserializer = super.findDeserializer(a);
if (deserializer == null) {
return null;
}
if (deserializer.equals(MyDeserializer.class)) {
return null;
}
return deserializer;
}
});
Now copied mapper will now ignore your custom deserializer (MyDeserializer.class) and use default implementation. You can use it inside deserialize method of your custom deserializer to avoid recursion by making copied mapper static or wire it if using Spring.
A simpler solution for me was to just add another bean of ObjectMapper and use that to deserialize the object (thanks to https://stackoverflow.com/users/1032167/varren comment) - in my case I was interested to either deserialize to its id (an int) or the whole object 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<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 entity that needs to be going through custom deserializer we need to configure it in the global ObjectMapper bean of the Spring Boot App in my case (e.g 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;
}
Here is a short solution using default ObjectMapper
private static final ObjectMapper MAPPER = new ObjectMapper(); // use default mapper / mapper without customization
public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyObject object = MAPPER.readValue(p, MyObject.class);
// do whatever you want
return object;
}
And please: There is really no need to use any String value or something else. All needed information are given by JsonParser, so use it.