Java 如何使用JsonTypeInfo和反序列化器来定制多态子类型的处理?

Java 如何使用JsonTypeInfo和反序列化器来定制多态子类型的处理?,java,json,polymorphism,jackson,Java,Json,Polymorphism,Jackson,在这种情况下,我需要定制一些JSON的序列化/反序列化。我已将其简化为一个可读的示例。我有一个容器类,它保存实现MyInterface的对象。在我的示例中,ClassA、ClassB、IntegerHolder和StringHolder实现了接口。通过将@JsonTypeInfo注释添加到我的接口(和容器): 通过为每个类注册类型名称,我可以成功地从JSON中读取/写入这些内容: {"type":"Container","items": [ {"type":"classA","aVa

在这种情况下,我需要定制一些JSON的序列化/反序列化。我已将其简化为一个可读的示例。我有一个容器类,它保存实现MyInterface的对象。在我的示例中,ClassA、ClassB、IntegerHolder和StringHolder实现了接口。通过将@JsonTypeInfo注释添加到我的接口(和容器):

通过为每个类注册类型名称,我可以成功地从JSON中读取/写入这些内容:

{"type":"Container","items":
    [   {"type":"classA","aValue":"AAA"},
        {"type":"classB","bValue":"BBB"},
        {"type":"intHolder","value":123},
        {"type":"stringHolder","value":"abc"} ] }
这一切都很好:)我的问题是,我想自定义intHolder和stringHolder的序列化,因为它们只是本地类型的包装。我的JSON将经常手工编辑,基本类型将被大量使用。因此,我想将JSON简化为:

{"type":"Container","items":
    [   {"type":"classA","aValue":"AAA"},
        {"type":"classB","bValue":"BBB"},
        123,
        "abc" ] }
我已经编写了一个序列化程序和反序列化程序(扩展了StdSeralizer和StdDeserializer),将它们放在一个SimpleModule中,并向映射程序注册了它(如图所示),在隔离状态下,它工作得很好。我的意思是,如果IntegerHolder和StringHolder是容器中唯一的对象,并且只有当我从接口中删除@JsonTypeInfo注释时,我才能序列化/反序列化它们。如果没有,则在写入JSON时会出现以下错误:

[main] ERROR MyTests - can't write the Container
com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type MyInterface (by serializer of type MyTests$MyInterfaceSerializer) (through reference chain: Container["items"])
    at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1047)
    at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:142)
    at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serializeTypedContents(ObjectArraySerializer.java:316)
    at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serializeContents(ObjectArraySerializer.java:217)
    at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serialize(ObjectArraySerializer.java:201)
    at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serialize(ObjectArraySerializer.java:25)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:575)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:552)
    at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:129)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3387)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2747)
    at MyTests.testItemSerializationDeserializationEquality(MyTests.java:51)
    at MyTests.testSerialization(MyTests.java:41)
但当然,删除了@JsonTypeInfo后,Jackson不知道如何反序列化ClassA和ClassB…因此在读取JSON时失败,原因是:

[main] INFO MyTests - {"type":"Container","items":[{"aValue":"AAA"},{"bValue":"BBB"},123,"abc"]}
[main] ERROR MyTests - can't read the Container
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of MyInterface, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.ByteArrayInputStream@37883b97; line: 1, column: 45] (through reference chain: Container["items"]->Object[][0])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:857)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:139)
    at MyTests$MyInterfaceDeserializer.deserialize(MyTests.java:163)
    at MyTests$MyInterfaceDeserializer.deserialize(MyTests.java:139)
我觉得Jackson可以做到这一点,我很快就可以将Jackson配置为序列化/反序列化这两组类,但到目前为止,我的尝试并没有取得成果

任何能让我朝正确方向前进的建议都将不胜感激…提前谢谢

以下是我的测试示例中的7个类:

MyInterface.java

import com.fasterxml.jackson.annotation.*;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface MyInterface
    {
    }
Container.java

import com.fasterxml.jackson.annotation.*;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public class Container
    {
    public Container()
        {
        }

    public Container(MyInterface... items)
        {
        this.items = items;
        }

    public MyInterface[] getItems()
        {
        return items;
        }

    public void setItems(MyInterface[] items)
        {
        this.items = items;
        }

    @Override
    public boolean equals(Object obj)
        {
        for (int i = 0; i < items.length; i++)
            if (!(items[i].equals(((Container)obj).items[i])))
                return false;
        return true;
        }

    private MyInterface[] items;
    }
ClassB.java

public class ClassB implements MyInterface
    {
    public String getbValue()
        {
        return _bValue;
        }

    public void setbValue(String bValue)
        {
        _bValue = bValue;
        }

    @Override
    public boolean equals(Object obj)
        {
        return obj instanceof ClassB && _bValue.equals(((ClassB)obj)._bValue);
        }

    private String _bValue;
    }
StringHolderClass.java

public class StringHolderClass implements MyInterface
    {
    public String getValue()
        {
        return _value;
        }

    public void setValue(String value)
        {
        _value = value;
        }

    @Override
    public boolean equals(Object obj)
        {
        return obj instanceof StringHolderClass && _value.equals(((StringHolderClass)obj)._value);
        }

    private String _value;
    }
public class IntegerHolderClass implements MyInterface
    {
    public int getValue()
        {
        return _value;
        }

    public void setValue(int value)
        {
        _value = value;
        }

    @Override
    public boolean equals(Object obj)
        {
        return obj instanceof IntegerHolderClass && _value.equals(((IntegerHolderClass)obj)._value);
        }

    private Integer _value;
    }
IntegerHolderClass.java

public class StringHolderClass implements MyInterface
    {
    public String getValue()
        {
        return _value;
        }

    public void setValue(String value)
        {
        _value = value;
        }

    @Override
    public boolean equals(Object obj)
        {
        return obj instanceof StringHolderClass && _value.equals(((StringHolderClass)obj)._value);
        }

    private String _value;
    }
public class IntegerHolderClass implements MyInterface
    {
    public int getValue()
        {
        return _value;
        }

    public void setValue(int value)
        {
        _value = value;
        }

    @Override
    public boolean equals(Object obj)
        {
        return obj instanceof IntegerHolderClass && _value.equals(((IntegerHolderClass)obj)._value);
        }

    private Integer _value;
    }
两种选择:

  • MyInterface
    定制反序列化程序,然后您不需要JsonTypeInfo-所有逻辑都将在反序列化程序中
  • 您可以尝试让
    IntegerHolder
    StringHolder
    实现另一个接口,比如说
    Holder
    ,并将
    JsonTypeInfo
    注释更改为:
    
    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME,property=“type”,defaultImpl=Holder.class)
    
    并为Holder.class指定一个反序列化器
  • 我已经拒绝了#1,因为其他人将扩展MyInterface,他们不需要编写/编辑反/序列化程序…他们应该能够依靠Jackson来为他们完成。我正在尝试#2,并且序列化正确。但是,在反序列化时,它在查找“type”参数以确定要反序列化的类型时失败。我觉得应该在我的HolderClassDeserializer中重写deserializeWithType(),因为我是在序列化程序中这样做的。这就是我现在正在尝试的。谢谢
    public class IntegerHolderClass implements MyInterface
        {
        public int getValue()
            {
            return _value;
            }
    
        public void setValue(int value)
            {
            _value = value;
            }
    
        @Override
        public boolean equals(Object obj)
            {
            return obj instanceof IntegerHolderClass && _value.equals(((IntegerHolderClass)obj)._value);
            }
    
        private Integer _value;
        }