Custom deserialization of List using Jackson - java

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!

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

jackson deserialize string to a parameterized map

I have a json string in the following structure:
{
"1": { ... },
"2": { ... },
"3": { ... }
}
where 1, 2 and 3 are identifiers.
I want to deserialize this string and cast it to the following type - Map<Integer, List<MyCustomPojo>>
The thing is that each value { ... } has its own structure, but I need to parse them all and cast to a common structure - MyCustomPojo.
I can do it by implementing a custom deserializer:
public class CustomMapDeserializer extends JsonDeserializer<Map<Integer, List<MyCustomPojo>>> {
#Override
public Map<Integer, List<MyCustomPojo>> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode root = jsonParser.getCodec().readTree(jsonParser);
// parse JsonNode and return Map<Integer, List<MyCustomPojo>>
}
Now I need to add this deserializer to a module and then register it with an object mapper.
SimpleModule module = new SimpleModule();
module.addDeserializer(Map.class, new CustomMapDeserializer()); // can’t specify type for map
objectMapper.registerModule(module);
The problem is that now it will be applied to all Map classes in my application. I want it to apply only to parameterized map - Map<Integer, List<MyCustomPojo>>.
Is it possible to do it?
Simplest solution should be to create a new class which inherit from Map and use it when you add deserializer
The way you're thinking of isn't going to work, and might need to rewrite some of the code to apply ONLY to your parameterized map. I don't want to give any false info, sorry I can't provide a solution, but I hope this comment will help others to provide a solution for other abled coders.

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.

Custom JSON mapping using Jackson

I have a POJO
class Product {
String name;
Size size;
}
So, I want to map a deserialize a JSON to my POJO. If I have both the attributes in my JSON, it is not a problem.
But in my case, sometimes size will not be a part of the JSON. There might be a third attribute 'type' based on which I will set my size. I do not want to include 'type' in my POJO. Are there any Jackson annotations which can do this?
write your custom Deserializers:
SimpleModule module =
new SimpleModule("ProductDeserializerModule",
new Version(1, 0, 0, null));
module.addDeserializer(Product.class, new ProductJsonDeserializer());
mapper = new ObjectMapper();
mapper.registerModule(module);
//...
class ProductJsonDeserializer extends JsonDeserializer<Product>
{
#Override
public Product deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
// handle here if exist a third attribute 'type' and create the product
}
}
More info here: http://wiki.fasterxml.com/JacksonHowToCustomDeserializers
Found a pretty simple solution for this!
When a JSON attribute is attempted to be mapped to my POJO's attribute, it just checks whether a setter exists for it.
For example, if there is an attribute type in JSON, it will try to hit a method named setType(obj) in my POJO, regardless of whether there exists an attribute named type.
This worked for me! I simply set my other attributes inside this setter.

Jackson deserialize json property into object

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).

Categories

Resources