Jackson 多态性:同时使用两种机制

问题描述 投票:0回答:1

我正在尝试反序列化相当奇怪的类型,类型解析可以用

JsonTypeInfo.Id.NAME
部分解决,但在某些情况下它不能完全解析类型,而是缩小选择范围(意味着两种类型共享
type
的共同值)场地)。但这些缩小的类型可以通过现有字段来解决(使用
JsonTypeInfo.Id.DEDUCTION
)。因此,我尝试通过引入使用
JsonTypeInfo.Id.NAME
注释所有类型的基本类型,然后引入缩小类型的中间超类型来实现此逻辑,该类型使用
JsonTypeInfo.Id.DEDUCTION
来完成其余的解析。

这是我想要实现的目标的最小示例:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonTest {
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
    @JsonSubTypes({
            @JsonSubTypes.Type(name = "a", value = A.class),
            @JsonSubTypes.Type(name = "b", value = B.class),
    })
    interface Base {
        String getType();
    }

    static class A implements Base {
        private String b1;

        @Override
        public String getType() {
            return "a";
        }

        public String getB1() {
            return b1;
        }

        public A setB1(String b1) {
            this.b1 = b1;
            return this;
        }
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = B1.class),
            @JsonSubTypes.Type(value = B2.class),
    })
    interface B extends Base {}

    static class B1 implements B {
        private String b1;

        public String getB1() {
            return b1;
        }

        public B1 setB1(String b1) {
            this.b1 = b1;
            return this;
        }

        @Override
        public String getType() {
            return "b";
        }
    }

    static class B2 implements B {
        private String b2;

        @Override
        public String getType() {
            return "b";
        }

        public String getB2() {
            return b2;
        }

        public B2 setB2(String b2) {
            this.b2 = b2;
            return this;
        }
    }


    public static void main(String[] args) throws JsonProcessingException {
        var om = new ObjectMapper();

        var b1 = new B1();
        b1.setB1("test");

        var str = om.writeValueAsString(b1);
        System.out.println(str);

        var deser = om.readValue(str, Base.class);
        System.out.println(deser);
    }
}

我得到的回应是:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `JacksonTest$B` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 21]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1887)
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1375)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4899)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3846)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3814)
    at JacksonTest.main(JacksonTest.java:87)

一个合理的尝试是尝试使用先推导,然后使用显式类型字段(至少在我的情况和这个简单的情况下是有意义的)。我们必须稍微重新排列类型:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonTest2 {
    @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = B2.class),
            @JsonSubTypes.Type(value = WithB1.class),
    })
    interface Base {
        String getType();
    }


    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
    @JsonSubTypes({
            @JsonSubTypes.Type(name = "a", value = A.class),
            @JsonSubTypes.Type(name = "b", value = B1.class),
    })
    interface WithB1 extends Base {
        String getB1();
    }

    static class A implements WithB1 {
        private String b1;

        @Override
        public String getType() {
            return "a";
        }

        public String getB1() {
            return b1;
        }

        public A setB1(String b1) {
            this.b1 = b1;
            return this;
        }
    }


    static class B1 implements WithB1 {
        private String b1;

        public String getB1() {
            return b1;
        }

        public B1 setB1(String b1) {
            this.b1 = b1;
            return this;
        }

        @Override
        public String getType() {
            return "b";
        }
    }

    static class B2 implements Base {
        private String b2;

        @Override
        public String getType() {
            return "b";
        }

        public String getB2() {
            return b2;
        }

        public B2 setB2(String b2) {
            this.b2 = b2;
            return this;
        }
    }


    public static void main(String[] args) throws JsonProcessingException {
        var om = new ObjectMapper();

        var b1 = new B1();
        b1.setB1("test");

        var str = om.writeValueAsString(b1);
        System.out.println(str);

        var deser = om.readValue(str, Base.class);

        System.out.println(deser);
    }
}

这里也没有运气:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Subtypes JacksonTest2$A and JacksonTest2$B1 have the same signature and cannot be uniquely deduced.
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findTypeDeserializer(BasicDeserializerFactory.java:1834)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:675)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:5030)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4900)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3846)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3814)
    at JacksonTest2.main(JacksonTest2.java:91)
Caused by: java.lang.IllegalStateException: Subtypes JacksonTest2$A and JacksonTest2$B1 have the same signature and cannot be uniquely deduced.
    at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.buildFingerprints(AsDeductionTypeDeserializer.java:90)
    at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.<init>(AsDeductionTypeDeserializer.java:49)
    at com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder.buildTypeDeserializer(StdTypeResolverBuilder.java:212)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findTypeDeserializer(BasicDeserializerFactory.java:1832)
    ... 6 more

杰克逊可以实现这一目标吗?

java jackson
1个回答
0
投票

回答我自己。

tl;dr:避免这样做:如果这是您生成的数据,请引入一些额外的类型信息,或者尝试说服数据供应商这样做。

似乎没有简单且开箱即用的方法可以做到这一点。

我最终摆脱了人为的类层次结构,并将自定义

@JsonTypeResolver
附加到您的类中并自己实现了这个解析器。附加
@JsonTypeIdResolver
是不够的,因为推演机制位于其之上。

以下是基础接口的注释:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
        @JsonSubTypes.Type(name = "a", value = A.class),
        @JsonSubTypes.Type(name = "b", value = B1.class),
        @JsonSubTypes.Type(name = "b", value = B2.class),
})

实现

TypeDeserializer
涉及扩展标准 Jackson
TypeDeserializer
之一并对其进行猴子修补。

第一个意图是通过 id 对传递给

subtypes
TypeResolverBuilder.buildTypeDeserializer
进行分组,将它们传递给
TypeDeserializer
,并在类型 id 不唯一时让它委托给
AsDeductionTypeDeserializer
。另一个需要注意的是,传递给
subtypes
StdTypeResolverBuilder.buildTypeDeserializer
永远不会有具有重复 ID 的类型,标准
SubtypeResolver
只是删除重复项。您还可以对其进行修补以在您的自定义中使用
TypeResolverBuilder

所以,这是您需要的猴子补丁类:

  • TypeResolverBuilder
    您将使用注释附加到您的类并返回自定义
    TypeDeserializer
  • TypeDeserializer
    本身,它将使用缩小的子类型列表将类型反序列化委托给
    AsDeductionTypeDeserializer
  • SubtypeResolver
    允许从
    collectAndResolveSubtypesByTypeId
  • 返回重复项

完整的答案对于答案来说相当大,所以我使用gist

来分享它
© www.soinside.com 2019 - 2024. All rights reserved.