动态杰克逊定制解串器

时间:2016-01-20 10:12:51

标签: java jackson

我的JSON看起来像这样:

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

我有两个反序列化器。第一个(JsonDeserializer<EntityImpl>)将检查JSON并提取由typeName属性提供的类型信息。

第二个反序列化器(JsonDeserializer<TestField>)用于反序列化field属性。此解串器需要知道以前提取的typeName值才能正常工作。

如何将类型信息从一个解串器传递给其他反序列化器?我尝试使用DeserializationContext,但我不知道如何将上下文从反序列化器A传递给B.

我目前的代码如下:

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;
    }
}

测试:

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());

    }

}

1 个答案:

答案 0 :(得分:1)

我目前的解决方案是这样调用以下映射器:

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

这样,先前加载的上下文数据映射将被放入用于以下解析过程的新上下文中。

完整示例:

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());

    }

}