From bfeebfeb693f1f2ef0d2f6abb34bdf0aeffa3af4 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 15 May 2023 08:34:22 +0200 Subject: feat(utils): add custom Jackson (de)serialization for polymorph class structures --- .../core/impl/json/EscapedJsonDeserializer.java | 97 ++++++++++++++++++++++ .../eaaf/core/impl/json/EscapedJsonSerializer.java | 73 ++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonDeserializer.java create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonSerializer.java diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonDeserializer.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonDeserializer.java new file mode 100644 index 00000000..5a081ec5 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonDeserializer.java @@ -0,0 +1,97 @@ +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); + + } + } + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonSerializer.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonSerializer.java new file mode 100644 index 00000000..a79f4d90 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/json/EscapedJsonSerializer.java @@ -0,0 +1,73 @@ +package at.gv.egiz.eaaf.core.impl.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collection; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + + +/** + * Custom Jackson Serializer to support generic types and serialize them as + * escaped Strings. + * + *

+ * Code was an example from stack-overflow. + *

+ * + * @author tlenz + * + */ +public class EscapedJsonSerializer extends StdSerializer { + private static final long serialVersionUID = 7334472154148003249L; + + public EscapedJsonSerializer() { + super((Class) null); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + StringWriter str = new StringWriter(); + JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str); + if (value instanceof Collection || value.getClass().isArray()) { + tempGen.writeStartArray(); + if (value instanceof Collection) { + for (Object it : (Collection) value) { + writeTree(gen, it, tempGen); + } + } else if (value.getClass().isArray()) { + for (Object it : (Object[]) value) { + writeTree(gen, it, tempGen); + } + } + tempGen.writeEndArray(); + } else { + provider.defaultSerializeValue(value, tempGen); + } + tempGen.flush(); + gen.writeString(str.toString()); + } + + @Override + public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, + TypeSerializer typeSer) throws IOException { + StringWriter str = new StringWriter(); + JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str); + writeTree(gen, value, tempGen); + tempGen.flush(); + gen.writeString(str.toString()); + } + + private void writeTree(JsonGenerator gen, Object it, JsonGenerator tempGen) throws IOException { + ObjectNode tree = ((ObjectMapper) gen.getCodec()).valueToTree(it); + tree.set("@class", new TextNode(it.getClass().getName())); + tempGen.writeTree(tree); + } +} -- cgit v1.2.3