0

My JSON looks like this:

{"typeName":"test","field":{"name":"42"}} 

I have two deserializers. The first one (JsonDeserializer<EntityImpl>) will examine the JSON and extract a type information which is provided by the typeName property.

The second deserializer (JsonDeserializer<TestField>) is used to deserialize the field property. This deserializer needs to know the previously extracted typeName value in order to work correctly.

How can i pass-along the type information from one deserializer to other deserializers? I tried to use DeserializationContext but i don't know how to pass along the Context from deserializer A to B.

My current code looks like this:

EntityImpl.java:

package de.jotschi.test; public class EntityImpl implements Entity { private String typeName; private TestField field; public String getTypeName() { return typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } public TestField getField() { return field; } public void setField(TestField field) { this.field = field; } } 

TestField.java:

package de.jotschi.test; public class TestField { String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } 

Test:

package de.jotschi.test; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import de.jotschi.test.EntityImpl; import de.jotschi.test.TestField; public class TestMapper2 { private InjectableValues getInjectableValue() { InjectableValues values = new InjectableValues() { @Override public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) { if ("data".equals(valueId.toString())) { return new HashMap<String, String>(); } return null; } }; return values; } @Test public void testMapper() throws IOException { ObjectMapper mapper = new ObjectMapper(); SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null)); idAsRefModule.addDeserializer(EntityImpl.class, new JsonDeserializer<EntityImpl>() { @Override public EntityImpl deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); System.out.println("Value: " + dataMap.get("test")); ObjectCodec codec = jp.getCodec(); JsonNode node = codec.readTree(jp); String type = node.get("typeName").asText(); dataMap.put("typeName", type); // How to pass on type information to TestField deserializer? The context is not reused for the next deserializer. // I assume that readValueAs fails since the codec.readTree method has already been invoked. //return jp.readValueAs(EntityImpl.class); // Alternatively the treeToValue method can be invoked in combination with the node. Unfortunately all information about the DeserializationContext is lost. I assume new context will be created. // How to reuse the old context? return codec.treeToValue(node, EntityImpl.class); } }); idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() { @Override public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Access type from context Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); System.out.println(dataMap.get("typeName")); ObjectCodec codec = p.getCodec(); JsonNode node = codec.readTree(p); return codec.treeToValue(node, TestField.class); } }); mapper.registerModule(idAsRefModule); mapper.setSerializationInclusion(Include.NON_NULL); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Setup the pojo EntityImpl impl = new EntityImpl(); impl.setTypeName("test"); TestField testField = new TestField(); testField.setName("42"); impl.setField(testField); // POJO -> JSON String json = mapper.writeValueAsString(impl); System.out.println(json); // JSON -> POJO Entity obj = mapper.reader(getInjectableValue()).forType(EntityImpl.class).readValue(json); System.out.println(obj.getClass().getName()); } } 
2
  • This type information manual handling is reinventing a wheel. Jackson can already do that. See this article cowtowncoder.com/blog/archives/2010/03/entry_372.html Commented Jan 20, 2016 at 10:17
  • My type information is not referring to a class. The type information is basically used to dynamically build the POJO manually within the TestField derserializer. I'm therefore not able to use the types system of jackson. Commented Jan 20, 2016 at 10:25

1 Answer 1

1

My current solution is call the following mapper this way:

 return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class); 

This way the previously loaded context data map is put into a new context that is used for the following parsing process.

Full example:

package de.jotschi.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; public class TestMapper2 { private InjectableValues getInjectableValue(final Map<String, String> dataMap) { InjectableValues values = new InjectableValues() { @Override public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) { if ("data".equals(valueId.toString())) { return dataMap; } return null; } }; return values; } @Test public void testMapper() throws IOException { ObjectMapper mapper = new ObjectMapper(); SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null)); idAsRefModule.addDeserializer(Entity.class, new JsonDeserializer<Entity>() { @Override public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); ObjectMapper mapper = (ObjectMapper) jp.getCodec(); ObjectNode obj = (ObjectNode) mapper.readTree(jp); String type = obj.get("typeName").asText(); dataMap.put("typeName", type); return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class); } }); idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() { @Override public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Access type from context Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); System.out.println("Type name: " + dataMap.get("typeName")); ObjectMapper mapper = (ObjectMapper) p.getCodec(); ObjectNode obj = (ObjectNode) mapper.readTree(p); // Custom deserialisation TestField field = new TestField(); field.setName(obj.get("name").asText()); // Delegate further deserialisation to other mapper field.setSubField(mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj.get("subField"), SubField.class)); return field; } }); mapper.registerModule(idAsRefModule); mapper.setSerializationInclusion(Include.NON_NULL); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Setup the pojo EntityImpl impl = new EntityImpl(); impl.setTypeName("test"); TestField testField = new TestField(); testField.setName("42"); SubField subField = new SubField(); subField.setName("sub"); testField.setSubField(subField); impl.setField(testField); // POJO -> JSON String json = mapper.writeValueAsString(impl); System.out.println(json); // JSON -> POJO Entity obj = mapper.reader(getInjectableValue(new HashMap<String, String>())).forType(Entity.class).readValue(json); assertNotNull("The enity must not be null", obj); assertNotNull(((EntityImpl) obj).getField()); assertEquals("42", ((EntityImpl) obj).getField().getName()); assertNotNull(((EntityImpl) obj).getField().getSubField()); assertEquals("sub", ((EntityImpl) obj).getField().getSubField().getName()); System.out.println(obj.getClass().getName()); } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.