Jackson deserialize json property into object - java

I need to deserialize the following json:
{
//...
"foo_id":1
//...
}
Into an object of class Foo with its id property set to the foo_id json property.
I need to do this within a custom deserializer.
What is the most easy way to accomplish this?
I was thinking to somehow "transform" the json to
{
//...
"foo_id":{
"id":1
}
//...
}
and then delegate this back to Jackson.
In this case, the object is of type Foo, but there are others which might not be of this class. Also, in this case, that json is a number, but I would like to support if it was a string as well.
So, I need a kind of generic way to do this, that's why I think delegating back to Jackson might be a good idea.
No annotations allowed. Suppose you're already writing the Deserializer for this property.

Take a look at this. Here is a code that I think might help you to get some ideas.
public class MyDeserializer extends JsonDeserializer< Message >
{
#Override
public Message deserialize( JsonParser jp, DeserializationContext arg1 ) throws IOException,
JsonProcessingException
{
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Message> subClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext())
{
Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
if ("foo_id".equals(name))
{
if(element.getValue().isInt())
subClass = FooInteger.Class;
break;
}
}
if (subClass == null) return null;
return mapper.readValue(root, subClass);
}
}

Have you considered use of mix-in annotations? With Jackson 2.2, you could also use converters to do two-step processing (#JsonDeserialize(converter=MyConverter.class).

Related

Jackson preprocess json before sending to deserialization

I have a JSON string which I would like to translate into POJO using ObjectMapper.readValue method.
The thing is that the input Json string contains keys which I would like to filter out before the deserialization.
I came across DelegatingDeserialization class which according to my understanding allows you to extend it and override one of the deserialize method to reconstruct the json input and then pass it on the chain.
The thing is that I try to enable this custom delegating deserializer by adding the
#JsonDeserialize(using = CustomDelegatingDeserialization.class) on top of my Pojo - is that the right way to instantiate it??
Here is a snippet of my custom delegator:
public static class CustomDeserializer extends DelegatingDeserializer {
public CustomDeserializer() {
super(null);
}
public CustomDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(defaultDeserializer);
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomDeserializer(newDelegatee);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(restructure(p), ctxt);
}
private JsonParser restructure(JsonParser jp) throws IOException {
...
return newJsonParser;
}
}
Am I taking the right path or there is a more fitting solution??
THank you!
EDIT 1
Another approach is to have a CustomJsonDeserializer extends JsonDeserializer<T> and override its deserialize method then reconstruct the Node and propagate it by returning codec.treeToValue(jsonNode, Pojo.class); this makes sense BUT it gets me into infinite loop! any idea why?
Assuming your POJO doesn't have a property that you would like to ignore you can use annotation #JsonIgnoreProperties(ignoreUnknown = true)for your class. That tells Jeckson to ignore properties that are not present in your POJO. Read more on the issue how to ignore some properties here: Jackson Unmarshalling JSON with Unknown Properties

How to create a custom deserializer in Jackson for a generic type?

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.

Custom deserialization of List using Jackson

I am trying to write a custom deserializer in order to trim down a big set of data I receive from somewhere else. I return a List of custom objects from the deserializer.
My question is, how do I do that, if this is my custom deserializer :
public class MyCustomDeserializer extends JsonDeserializer<List<CustomClass>> { ... }
I certainly can't do this :
final SimpleModule module = new SimpleModule();
module.addDeserializer(List<CustomClass>.class, new MyCustomDeserializer());
Will something like this work ?
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
If this indeed works, I find it a bit confusing and "dangerous" ? Isn't the deserialization done inside the asList method invocation ? So it basically maps a List to an array[] ?
I learned about TypeReference so I can probably use that like so :
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
but I heard it is slower.
I also don't want to create a container for the list, and return that in the deserialization because that means it will be wrapped in another json object, and I simply want my endpoint to produce something like :
[{object1}, {object2}]
// instead of
{"Output" : [{object1}, {object2}]}
EDIT:
It seems that I have misinterpreted how jackson is using my deserializer in both cases :
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
// or
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
It looks like the deserializer is called twice, once for each object in the array. I thought that the entire array would be considered as a whole. To clear the confusion, here is what I mean:
The json I receive and try to deserialize looks like so :
[
{
"Data" : {
"id" : "someId",
"otherThing" : "someOtherThing"
},
"Message" : "OK"
},
{
"Data" : null,
"Message" : "Object not found for id blabla"
}
]
and so I though this is what I would have inside my deserializer, but as I said before it seems that i actually get each "entry" from that array and call it multiple times.
First of all, If you registered your custom deserializer using annotation on the bean CustomClass then the deserializer should handle one instance of CustomClass and not a collection and thus should be defined:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
...
}
}
and now you can use Jackson's type factory to pass the mapper the required type information
JavaType customClassCollection = objectMapper.getTypeFactory().constructCollectionType(List.class, CustomClass.class);
List<CustomClass> beanList = (List<CustomClass>)objectMapper.readValue(stringBean, customClassCollection);
I worked it out by adding a custom deserializer to an attribute in my model class and using JsonDeserialize annotation's contentUsing() method, like so:
#JsonDeserialize(contentUsing = MyCustomDeserializer.class)
private List<CustomClass> customClassObjList;
where MyCustomDeserializer class is a custom Jackson JSON deserializer defined as:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
...
}
}
These two lines will just do enough.
ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(stringBean);
List<CustomClass> response = objectMapper.convertValue(arrayNode, List.class);
Thank me later!

How to create a general JsonDeserializer

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.

Jersey, Jackson and JAX-RS POST multiple JSON formats

I am trying to be able to define the following code:
public class MyObject {
private String name;
... // Other attributes
}
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(List<MyObject> myObjects) {
// Do some stuff there
}
}
I know that I need to use:
DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true
to setup correctly my object mapper to be able to accept single value as array on my rest resources. I succeed to setup that part.
My problem with this approach is that the following content is not differentiable:
{
"name": "a name",
... // other attributes
}
and
[{
"name": "a name",
... // other attributes
}]
will result into a list (List) of size one. Then, in the method create(List myObjects), I will not be able to do the difference between the List and the Single Object sent to the Rest Resource.
Then, my question is how to do something like that. The idea is to have only one #POST that accepts both Arrays and Single values?
Ideally, I will get rid of the configuration of the ObjectMapper to avoid letting the possibility to set Single Object into the other level of the JSON document. For example, I do not want to allow that:
{
...
"attributes": {
...
}
}
where normally this format should be mandatory:
{
...
"attributes": [{
...
}]
}
Based on that, I tried to put in place an object wrapper of my List to set if I am able to the difference between the list and the object. With something like that:
public class ObjectWrapper<T> {
private List<T> list;
private T object;
public boolean isObject() {
return list == null;
}
}
with the resource that becomes:
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(ObjectWrapper myObjects) {
// Do some stuff there
}
}
and trying to put in place the deserialization of my content through the JAX-RS/Jersey/Jackson mechanisms. If I let the solution as it is now, the deserialization fails due to the fact that the JSON format expected is the following:
{
"list": [{
"name": "a name",
... // other attributes
}]
}
Then I tried to write a custom deserializer but I am a bit lost in this task. I have something like that:
public class ObjectWrapperDeserializer<T> extends JsonDeserializer<T> {
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
... // What to put there to deserialize Array or Object
}
}
I just want to deserialize the root level to set the content deserialized into the object wrapper. I also want to keep the feature configured in a class annotated with #ApplicationPath when the configuraiton of the different #Provider are done.
I hope that all the info will give a sufficient picture of what I want to do and what I already tested.
Waiting for suggestion on how to do a resource that accept Arrays or Objects on the same path.
Thanks a lot in advance.
Ok, finally I succeed to put in place a mechanism that do exactly what I am looking for. But, I am not sure if there are negative consequences such the performance or such things.
First, I defined a class that can accept both List or Single Object:
public class RootWrapper<T> {
private List<T> list;
private T object;
}
Then, I need a custom deserializer that is able to know which kind of T type to deserialize and to handle the collection or the single object.
public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
private Class contentType;
public RootWrapperDeserializer(Class contentType) {
this.contentType = contentType;
}
#Override
public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Retrieve the object mapper and read the tree.
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode root = mapper.readTree(jp);
RootWrapper wrapper = new RootWrapper();
// Check if the root received is an array.
if (root.isArray()) {
List list = new LinkedList();
// Deserialize each node of the array using the type expected.
Iterator<JsonNode> rootIterator = root.getElements();
while (rootIterator.hasNext()) {
list.add(mapper.readValue(rootIterator.next(), contentType));
}
wrapper.setList(list);
}
// Deserialize the single object.
else {
wrapper.setObject(mapper.readValue(root, contentType));
}
return wrapper;
}
}
As far as I know, I try to only deserialize the root level manually and then let Jackson take the next operations in charge. I only have to know which real type I expect to be present in the Wrapper.
At this stage, I need a way to tell Jersey/Jackson which deserializer to use. One way I found for that is to create a sort of deserializer registry where are stored the type to deserialize with the right deserializer. I extended the Deserializers.Base class for that.
public class CustomDeserializers extends Deserializers.Base {
// Deserializers caching
private Map<Class, RootWrapperDeserializer> deserializers = new HashMap<>();
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, DeserializerProvider provider,
BeanDescription beanDesc, BeanProperty property) throws JsonMappingException {
// Check if we have to provide a deserializer
if (type.getRawClass() == RootWrapper.class) {
// Check the deserializer cache
if (deserializers.containsKey(type.getRawClass())) {
return deserializers.get(type.getRawClass());
}
else {
// Create the new deserializer and cache it.
RootWrapperDeserializer deserializer =
new RootWrapperDeserializer(type.containedType(0).getRawClass());
deserializers.put(type.getRawClass(), deserializer);
return deserializer;
}
}
return null;
}
}
Ok, then I have my deserializers registry that create new deserializer only on demand and keep them once created. What I am not sure about that approach is if there is any concurrency issue. I know that Jackson do a lot of caching and do not call every time the findBeanDeserializer once it was called a first time on a specific deserialization context.
Now I have created my different classes, I need to do some plumbing to combine everything together. In a provider where I create the ObjectMapper, I can setup the deserializers registry to the created object mapper like below:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper jacksonObjectMapper;
public JsonObjectMapper() {
jacksonObjectMapper = new ObjectMapper();
// Do some custom configuration...
// Configure a new deserializer registry
jacksonObjectMapper.setDeserializerProvider(
jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers(
new RootArrayObjectDeserializers()
)
);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return jacksonObjectMapper;
}
}
Then, I can also define my #ApplicationPath that is my REST application like following:
public abstract class AbstractRestApplication extends Application {
private Set<Class<?>> classes = new HashSet<>();
public AbstractRestApplication() {
classes.add(JacksonFeature.class);
classes.add(JsonObjectMapper.class);
addResources(classes);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
final Set<Object> singletons = new HashSet<>(1);
singletons.add(new JacksonJsonProvider());
return singletons;
}
private void addResources(Set<Class<?>> classes) {
classes.add(SomeRestResource.class);
// ...
}
}
Now, everything is in place and I can write a REST resource method like that:
#POST
#Path("somePath")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(RootWrapper<SpecificClass> wrapper) {
if (wrapper.isObject()) {
// Do something for one single object
SpecificClass sc = wrapper.getObject();
// ...
return Response.ok(resultSingleObject).build();
}
else {
// Do something for list of objects
for (SpecificClass sc = wrapper.getList()) {
// ...
}
return Response.ok(resultList).build();
}
}
That's all. Do not hesitate to comment the solution. Feedbacks are really welcome especially around the way of deserialization process where I am really not sure that it is safe for performance and concurrency.

Categories

Resources