Jackson-抽象引用列表无法反序列化

时间:2018-08-21 08:51:43

标签: java json jackson

我在Jackson图书馆遇到问题,希望在特定问题上获得您的帮助。

我设法在一个孤立的测试用例中重现了我的问题。这与杰克逊在引用后面有真实对象时无法反序列化抽象引用列表有关。

代码无效

public class TestListOfAbstractRef {

        public interface TestInterface {}

        @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
        public static class Root {
            public String id;
            public Root(){}
        }

        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
        public static class Test1 extends Root implements TestInterface {
            public String test1_param = "test1_param";
            public Test1(){}
        }
        public static class Test2 extends Root implements TestInterface {
            public String test2_param = "test2_param";
            public Test2(){}
        }

        public static class Test3 extends Root{
            public Test3(){}
            @JsonIdentityReference(alwaysAsId = true)
            public List<TestInterface> list = new ArrayList<>();
        }

        @Test
        public void TestListOfAbstractRef_SerializationAndDeserialization() throws IOException {
            Test1 test1 = new Test1();
            test1.id = "#1";
            Test2 test2 = new Test2();
            test2.id = "#2";
            Test3 test3 = new Test3();
            test3.id = "#3";
            test3.list.add(test1);
            test3.list.add(test2);
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY);
            String test3_string = objectMapper.writer().withDefaultPrettyPrinter().writeValueAsString(new Object[]{test1,test2,test3});
            Object[] objects = objectMapper.readValue(test3_string, Object[].class);
        }

}

序列化的结果

[ {
  "@class" : "TestListOfAbstractRef$Test1",
  "id" : "#1",
  "test1_param" : "test1_param"
}, {
  "@class" : "TestListOfAbstractRef$Test2",
  "id" : "#2",
  "test2_param" : "test2_param"
}, {
  "@class" : "TestListOfAbstractRef$Test3",
  "id" : "#3",
  "list" : [ "#1", "#2" ]
} ]

杰克逊产生的错误

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `TestListOfAbstractRef$TestInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (String)"[ {
  "@class" : "TestListOfAbstractRef$Test1",
  "id" : "#1",
  "test1_param" : "test1_param"
}, {
  "@class" : "TestListOfAbstractRef$Test2",
  "id" : "#2",
  "test2_param" : "test2_param"
}, {
  "@class" : "TestListOfAbstractRef$Test3",
  "id" : "#3",
  "list" : [ "#1", "#2" ]
} ]"; line: 12, column: 14] (through reference chain: java.lang.Object[][2]->TestListOfAbstractRef$Test3["list"]->java.util.ArrayList[0])

    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:286)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1257)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:192)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:193)
    at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:712)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:197)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
    at TestListOfAbstractRef.TestListOfAbstractRef_SerializationAndDeserialization(TestListOfAbstractRef.java:53)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:515)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:131)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:107)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:136)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:107)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:136)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:107)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:52)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:185)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:153)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:167)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:146)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:93)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

如何解决此问题?

我找到了解决此问题的两种方法

1°)用Root类替换列表中的TestInterface

    public static class Test3 extends Root{
        public Test3(){}
        @JsonIdentityReference(alwaysAsId = true)
        public List<Root> list = new ArrayList<>();
    }

在这种情况下,由于Root接口不是一个抽象元素,并且可以构造它,因此不再存在问题。但这不是解决方案,而是解决方法。

2°)将默认类型更改为NON_FINAL并将Test3保存为没有ID引用的整个文件

public class TestListOfAbstractRef {


    public interface TestInterface {}

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    public static class Root {
        public String id;
        public Root(){}
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
    public static class Test1 extends Root implements TestInterface {
        public String test1_param = "test1_param";
        public Test1(){}
    }
    public static class Test2 extends Root implements TestInterface {
        public String test2_param = "test2_param";
        public Test2(){}
    }

    public static class Test3 extends Root{
        public Test3(){}
        public List<TestInterface> list = new ArrayList<>();
    }

    @Test
    public void TestListOfAbstractRef_SerializationAndDeserialization() throws IOException {
        Test1 test1 = new Test1();
        test1.id = "#1";
        Test2 test2 = new Test2();
        test2.id = "#2";
        Test3 test3 = new Test3();
        test3.id = "#3";
        test3.list.add(test1);
        test3.list.add(test2);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        String test3_string = objectMapper.writer().withDefaultPrettyPrinter().writeValueAsString(test3);
        Test3 test31 = objectMapper.readValue(test3_string, Test3.class);
    }

}

这样,所有内容都在文件中,类型信息也都在文件中,Jackson可以反序列化元素没有问题。

危险

找到解决方案时为什么要寻求帮助?

简单,这种类模式并不是我所说的粗俗,实际上,这是我制作的脚本的结果,这些脚本生成了这些类和这种模式,我无法真正更改其方式,因为这种模式是正确并符合我要翻译的内容。我要问的是序列化和反序列化这种模式的解决方案。

您对此有任何想法吗?


编辑

找到了解决该问题的另一种方法,只需为所涉及的接口(或抽象类)创建自定义解串器。在此反序列化器中,您可以在上下文中使用objectidresolver自行解析引用。这个解决方案确实不是很优雅,但是可以解决问题。就我而言,这意味着我需要为我拥有的每个接口自动生成一个自定义解串器。我觉得这对于这么简单的事情来说确实是很大的努力...

public class TestListOfAbstractRef {

    public static class TestInterfaceDeserializer extends StdDeserializer<TestInterface> {

        protected TestInterfaceDeserializer(Class<?> vc) {
            super(vc);
        }

        protected TestInterfaceDeserializer() {
            this(null);
        }

        @Override
        public TestInterface deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            ReadableObjectId roid = ctxt.findObjectId(p.getText(), new PropertyBasedObjectIdGenerator(Object.class) ,new SimpleObjectIdResolver());
            return (TestInterface) roid.resolve();
        }
    }

    public interface TestInterface {
    }

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    public static class Root {
        public String id;

        public Root() {
        }
    }

    public static class Test1 extends Root implements TestInterface {
        public String test1_param = "test1_param";

        public Test1() {
        }
    }

    public static class Test2 extends Root implements TestInterface {
        public String test2_param = "test2_param";

        public Test2() {
        }
    }

    public static class Test3 extends Root {
        public Test3() {
        }

        @JsonIdentityReference(alwaysAsId = true)
        public List<TestInterface> list = new ArrayList<>();
    }

    @Test
    public void TestListOfAbstractRef_SerializationAndDeserialization() throws IOException {
        Test1 test1 = new Test1();
        test1.id = "#1";
        Test2 test2 = new Test2();
        test2.id = "#2";
        Test3 test3 = new Test3();
        test3.id = "#3";
        test3.list.add(test1);
        test3.list.add(test2);
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(TestInterface.class, new TestInterfaceDeserializer());
        mapper.registerModule(module);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY);

        String test3_string = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(new Object[]{test1, test2, test3});
        Object[] objects = mapper.readValue(test3_string, Object[].class);
    }

}

0 个答案:

没有答案