How use jackson ObjectMapper inside custom deserializer?
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:
@Overridepublic 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.
In order to use your own ObjectMapper
inside a custom deserializer, you can use Jackson Mix-in Annotations (the DefaultJsonDeserializer
interface) to dynamically remove the custom deserializer from the POJO
classes, avoiding the StackOverflowError
that would otherwise be thrown as a result of objectMapper.readValue(JsonParser, Class<T>)
.
public class MyDeserializer extends JsonDeserializer<MyInterface> { private static final ObjectMapper objectMapper = new ObjectMapper(); static { objectMapper.addMixIn(MySuccess.class, DefaultJsonDeserializer.class); objectMapper.addMixIn(MyFailure.class, DefaultJsonDeserializer.class); } @Override public MyInterface deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (jp.getCodec().<JsonNode>readTree(jp).has("custom_field")) { return objectMapper.readValue(jp, MyFailure.class); } else { return objectMapper.readValue(jp, MySuccess.class); } } @JsonDeserialize private interface DefaultJsonDeserializer { // Reset default json deserializer }}
This did the trick for me:
ctxt.readValue(node, MyFailure.class)