package at.gv.egiz.eaaf.core.impl.json; import java.io.IOException; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; /** * Custom Jackson Deserializer to support generic types serialized as escaped * Strings. * *

* Code was an example from stack-overflow.
*
* This implementation checks if the generic class type is compatible to * original interface, by using Class.isAssignableFrom(...). *

* * @author tlenz * */ public class EscapedJsonDeserializer extends JsonDeserializer implements ContextualDeserializer { private final Map> cachedDeserializers = new HashMap<>(); @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { throw new IllegalArgumentException( "EscapedJsonDeserializer should delegate deserialization for concrete class"); } @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { JavaType type = (ctxt.getContextualType() != null) ? ctxt.getContextualType() : property.getMember().getType(); return cachedDeserializers.computeIfAbsent(type, (a) -> new InnerDeserializer(type)); } private static class InnerDeserializer extends JsonDeserializer { private final JavaType javaType; private InnerDeserializer(JavaType javaType) { this.javaType = javaType; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String string = p.readValueAs(String.class); return ((ObjectMapper) p.getCodec()).readValue(string, javaType); } @Override public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { String str = p.readValueAs(String.class); TreeNode root = ((ObjectMapper) p.getCodec()).readTree(str); try { Class clz = Class.forName(((TextNode) root.get("@class")).asText()); if (this.javaType.getRawClass().isAssignableFrom(clz)) { // remove '@class' before deserialization because it's unknown in target class if (root.isObject()) { ((ObjectNode) root).remove("@class"); } Object newJsonNode = p.getCodec().treeToValue(root, clz); return newJsonNode; } else { throw new RuntimeException("Class looks supect because it does not match to interface."); } } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } }