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?
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With