Jackson 无法创建已在 ObjectMapper 中注册的 class 的实例

Jackson cannot create instance of a class already registered in ObjectMapper

我有一个 parent object 和一个 child object。 parent object 可能多次包含相同的 child object,所以我只序列化 child object 一次,下一个实例是仅由其 ID 引用。当我从 child object 中删除 @JsonIdentityInfo 注释时,object 反序列化没有错误。对我来说,这感觉像是 Jackson 的错误,但也许有人在我的代码中发现了错误。我做了一个显示错误的小例子(使用的 Jackson 版本是 2.9.4): parent class:

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.ArrayList;
import java.util.Collection;
import lombok.Getter;
import lombok.Setter;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
        property = "type", defaultImpl = TestClassParent.class)
public class TestClassParent {

    @Getter
    @Setter
    private ITestClassChild child1;

    @Getter
    @Setter
    private Collection<ITestClassChild> children;

    public TestClassParent(){
        child1 = new TestClassChild();
        children = new ArrayList<>();
        children.add(child1);
    }
}

child class:

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Getter;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
        property = "type", defaultImpl = TestClassChild.class)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, 
        property="id")
public class TestClassChild implements ITestClassChild{

    @Getter
    private String id;

    public TestClassChild(){
        id = "1";
    }
}

界面:

import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
        property = "type", defaultImpl = ITestClassChild.class)
public interface ITestClassChild {
    public String getId();
}

测试用例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.junit.Test;

public class TestClassImportExportTest{

    @Test
    public void test() throws JsonProcessingException, IOException{
        ObjectMapper om = new ObjectMapper();
        om.registerSubtypes(
                TestClassChild.class
        );
        TestClassParent original = new TestClassParent();
        String json = om.writerWithDefaultPrettyPrinter().writeValueAsString(original);
        TestClassParent imported = om.readValue(json, TestClassParent.class);
    }

}

执行测试结果报错如下:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `json.ITestClassChild` (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)"{
  "type" : "TestClassParent",
  "child1" : {
    "type" : "TestClassChild",
    "id" : "1"
  },
  "children" : [ "1" ]
}"; line: 7, column: 18] (through reference chain: json.TestClassParent["children"]->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.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:178)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:88)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:288)
    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.MethodProperty.deserializeAndSet(MethodProperty.java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:189)
    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.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1171)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
    at json.TestClassImportExportTest.test(TestClassImportExportTest.java:18)

如上所述,从 TestClassChild 中删除 @JsonIdentityInfo 注释可以使测试用例无错通过。但是,我需要注释才能正确导入。该错误似乎是由使用 collection + interface + id 引起的,删除其中任何一个似乎都有效,我很乐意提供任何帮助!

Jackson 无法构造 ITestClassChild 的对象,因为它是一个接口,与 JsonIdentityInfo 一样,它试图保持双向关系。

您可以在此处阅读 JsonIdentityInfo - Jackson Relationships

with interface需要指定使用@JsonSubTypes的Type,例如-

@JsonSubTypes({ @Type(value = TestClassChild.class, name = "child1")}) public interface ITestClassChild

参考此 blog 以获得更好的解释。

二手 - 杰克逊 2.8。下面是我的代码我仍然使用输入

得到相同的异常

{ "type" : "TestClassParent", "child" : { "type" : "TestClassChild", "id" : "1" } }

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = TestClassParent.class)
class TestClassParent {

    private ITestClassChild child;

    /*private Collection<ITestClassChild> children;*/

    public TestClassParent(){
        //child = new TestClassChild();
        /*children = new ArrayList<>();
        children.add(child1);*/
    }

    public ITestClassChild getChild() {
        return child;
    }

    public void setChild(ITestClassChild child) {
        this.child = child;
    }

}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
        property = "type", defaultImpl = ITestClassChild.class)
interface ITestClassChild {
    public String getId();
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = TestClassChild.class)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, 
property="id")
class TestClassChild implements ITestClassChild {

private String id;

    public TestClassChild(){
    id = "1";
    }

    @Override
    public String getId() {

        return id;
    }
}

但是如果你使用下面的输入你不会得到异常因为它忽略了 属性.

{ "type" : "TestClassParent", }

因此,以下解决方法应该有效:

我为每个子 class 添加了一个自定义反序列化器(用 @JsonDeserialize(using = TestClassChildDeserializer.class) 注释 class )和一个接口(也注释了同样的方式)。然后我反序列化对象(也许有一种方法可以使用默认的反序列化器?)并将对象添加到接口反序列化器中的静态哈希图中。

反序列化器只是获取节点的textValue 并返回该节点的HashMap 中的值。子反序列化器反序列化对象并将其保存到 HashMap 中。

它非常丑陋,但暂时有效(虽然我需要在反序列化后清空 HashMap,而且它也不是线程安全的...)。

我也尝试了这里提出的解决方案:https://github.com/FasterXML/jackson-databind/issues/1641 无济于事。