【问题标题】:Dynamic Jackson Custom Deserializer动态杰克逊自定义解串器
【发布时间】:2016-04-26 03:04:07
【问题描述】:

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

    }

}

【问题讨论】:

  • 这种类型的信息人工处理是在重新发明轮子。杰克逊已经可以做到这一点。看这篇文章cowtowncoder.com/blog/archives/2010/03/entry_372.html
  • 我的类型信息不是指一个类。类型信息主要用于在 TestField 反序列化器中手动动态构建 POJO。因此,我无法使用杰克逊的类型系统。

标签: java jackson


【解决方案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());

    }

}

【讨论】:

    猜你喜欢
    • 2019-10-24
    • 1970-01-01
    • 2021-05-31
    • 1970-01-01
    • 2019-05-25
    • 2013-02-21
    • 1970-01-01
    • 2014-10-12
    • 1970-01-01
    相关资源
    最近更新 更多