Custom JSON mapping using Jackson - java

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.

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 custom deserialiser delegate back to default one

In a custom Jackson deserialiser, is there a way to delegate certain properties back to the default deserialiser?
#Override
public final T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectNode node = jp.getCodec().readTree(jp);
T type = createType();
//custom deserialise some fields here
...
// Is there a way to delegate everything else back to Jackson?
ObjectNode nodeToDelegate = node.get("someField");
// delegate back to jackson and deserialise into `type`
// nodeToDelegate can be anything - Number / Object / Array / etc.
}
p.s. I do need custom deserialiser and cannot use type annotations.
You can use following code to achieve this.
JsonParser parser = nodeToDelegate.traverse();
parser.setCodec(jp.getCodec());
parser.readValueAs(<Type>.class);

How to use Jackson to validate duplicated properties?

I'm using Jackson JSON library to convert some JSON objects to POJO classes. The problem is, when I use JSON Objects with duplicated properties like:
{
"name":"xiaopang",
"email":"xiaopang1#123.com",
"email":"xiaopang2#123.com"
}
Jackson report the last email pair "email":"xiaopang2#123.com" and then parse the object.
I've learned from Does JSON syntax allow duplicate keys in an object? that what happens when deserializing a JSON object with duplicate properties depends on the library implementation, either throwing an error or using the last one for duplicate key.
Despite overheads of tracking all properties, is there any way to tell Jackson to report an error or exception such as "Duplicate key" in this case?
Use JsonParser.Feature.STRICT_DUPLICATE_DETECTION
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
MyPOJO result = mapper.readValue(json, MyPOJO.class);
Results in:
Exception in thread "main" com.fasterxml.jackson.core.JsonParseException: Duplicate field 'email'
You can also try to use DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY (more info) It will be trigggered if you deserialize your json string/input to jackson json tree first and then to you POJO. Can combine it with custom JsonDeserializer like this:
private static class MyPojoDeserializer extends JsonDeserializer<MyPOJO>{
#Override
public MyPOJO deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException{
JsonNode tree = p.readValueAsTree();
return p.getCodec().treeToValue(tree, MyPOJO.class);
}
}
Setup it once and use it same way as before:
// setup ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
SimpleModule module = new SimpleModule();
module.addDeserializer(MyPOJO.class,new MyPojoDeserializer() );
mapper.registerModule(module);
// use
mapper.readValue(json, MyPOJO.class);
Result:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate field 'email' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled
Other options would be to implement all the logic yourself in custom deserializer or in you POJO setter methods.

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.

Categories

Resources