Reputation: 38643
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
}
}
Upvotes: 4
Views: 7574
Reputation: 1314
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);
}
}
Upvotes: 4
Reputation: 38643
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?
Upvotes: 1
Reputation: 425348
Essentially, there are only 2 cases you need to cater for, Object
and Object[]
, for which you can always deserialize to:
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
Upvotes: 1
Reputation: 54
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.
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.
Upvotes: 0
Reputation: 1332
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
Upvotes: 0
Reputation: 5213
If you know the message structure in advance, you can use this tool to easily generate POJO
s 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);
Upvotes: 0