简体   繁体   English

动态Jackson自定义反序列化器

[英]Dynamic Jackson Custom Deserializer

My JSON looks like this: 我的JSON看起来像这样:

{"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. 第一个( JsonDeserializer<EntityImpl> )将检查JSON并提取由typeName属性提供的类型信息。

The second deserializer ( JsonDeserializer<TestField> ) is used to deserialize the field property. 第二个反序列化器( JsonDeserializer<TestField> )用于反序列化field属性。 This deserializer needs to know the previously extracted typeName value in order to work correctly. 该解串器需要知道先前提取的typeName值才能正常工作。

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. 我尝试使用DeserializationContext,但是我不知道如何将Context从反序列化器A传递到B。

My current code looks like this: 我当前的代码如下所示:

EntityImpl.java: 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: 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());

    }

}

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

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM