Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How use jackson ObjectMapper inside custom deserializer?

Tags:

I try to write custom jackson deserializer. I want "look" at one field and perform auto deserialization to class, see example below:

import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.mypackage.MyInterface; import com.mypackage.MyFailure; import com.mypackage.MySuccess;  import java.io.IOException;  public class MyDeserializer extends JsonDeserializer<MyInterface> {      @Override     public MyInterface deserialize(JsonParser jp, DeserializationContext ctxt)             throws IOException, JsonProcessingException {         ObjectCodec codec = jp.getCodec();         JsonNode node = codec.readTree(jp);         if (node.has("custom_field")) {             return codec.treeToValue(node, MyFailure.class);         } else {             return codec.treeToValue(node, MySuccess.class);         }     } } 

Pojos:

public class MyFailure implements MyInterface {}  public class MySuccess implements MyInterface {}  @JsonDeserialize(using = MyDeserializer.class) public interface MyInterface {} 

And I got StackOverflowError. In understand that codec.treeToValue call same deserializer. Is there a way to use codec.treeToValue or ObjectMapper.readValue(String,Class<T>) inside custome deseralizer?

like image 484
Cherry Avatar asked Sep 13 '15 16:09

Cherry


People also ask

Can ObjectMapper be reused?

Creating an ObjectMapper is quite expensive. Therefore it is recommended that you reuse your ObjectMapper instance. The Jackon ObjectMapper is thread-safe so it can safely be reused.

How do I read JSON file with ObjectMapper?

Read Object From JSON via URL ObjectMapper objectMapper = new ObjectMapper(); URL url = new URL("file:data/car. json"); Car car = objectMapper. readValue(url, Car. class);


1 Answers

The immediate problem seems to be that the @JsonDeserialize(using=...) is being picked up for your implementations of MyInterface as well as MyInterface itself: hence the endless loop.

You can fix this my overriding the setting in each implementation:

@JsonDeserialize(using=JsonDeserializer.None.class) public static class MySuccess implements MyInterface { } 

Or by using a module instead of an annotation to configure the deserialization (and removing the annotation from MyInterface):

mapper.registerModule(new SimpleModule() {{     addDeserializer(MyInterface.class, new MyDeserializer()); }}); 

On a side-note, you might also consider extending StdNodeBasedDeserializer to implement deserialization based on JsonNode. For example:

@Override public MyInterface convert(JsonNode root, DeserializationContext ctxt) throws IOException {     java.lang.reflect.Type targetType;     if (root.has("custom_field")) {         targetType = MyFailure.class;     } else {         targetType = MySuccess.class;     }     JavaType jacksonType = ctxt.getTypeFactory().constructType(targetType);     JsonDeserializer<?> deserializer = ctxt.findRootValueDeserializer(jacksonType);     JsonParser nodeParser = root.traverse(ctxt.getParser().getCodec());     nodeParser.nextToken();     return (MyInterface) deserializer.deserialize(nodeParser, ctxt); } 

There are a bunch of improvements to make to this custom deserializer, especially regarding tracking the context of the deserialization etc., but this should provide the functionality you're asking for.

like image 159
araqnid Avatar answered Oct 03 '22 00:10

araqnid