将ReferenceTypeDeserializer与Jackson和Spring一起使用以还原序列化泛型型别

x6492ojm  于 2022-11-08  发布在  Spring
关注(0)|答案(2)|浏览(224)

我想我在这里遗漏了一些明显的东西,但是我似乎不能使用Spring/Kotlin/Jackson反序列化一个简单的泛型容器。
所讨论的数据类型非常简单:

@JsonDeserialize(using = PatchableDeserializer::class)
sealed class Patchable<T> {
    class Undefined<T>: Patchable<T>()
    class Null<T>: Patchable<T>()
    data class Present<T>(val content: T): Patchable<T>()
    // …
}

解串器扩展了ReferenceTypeDeserializer,就像jdk 8模块的OptionalDeserializer一样。

class PatchableDeserializer(javaType: JavaType, vi: ValueInstantiator, typeDeser: TypeDeserializer, deser: JsonDeserializer<*> ):
        ReferenceTypeDeserializer<Patchable<*>>(javaType, vi, typeDeser, deser) {
    // …
}

我假设Jackson会在这里填写PatchableDeserializer的构造函数参数,但是,情况似乎并非如此:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'my.namespace.PatchableDeserializer': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.JavaType' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

我会假设Jackson提供了javaType的值,因为我在编译时无法知道它。
下面是我用来测试的代码,它会生成上面的异常:

@RunWith(SpringRunner::class)
@JsonTest
class PatchableTest {
    @Autowired
    lateinit var objectMapper: ObjectMapper

    @Test
    fun patchableDeserialisesStringValue() {
        val value: Patchable<String> = objectMapper.readValue("\"null\"", object: TypeReference<Patchable<String>>() {})
        assertTrue(value.isPresent())
        assertEquals("null", value.unsafeGetValue())
    }
}

我错过了什么?而且,我在网上找了很长时间才找到一些关于如何反序列化泛型类型的信息,所以如果有人有关于如何为泛型类型编写自定义反序列化器的指针,我将非常感激。

pbpqsu0x

pbpqsu0x1#

泛型容器的还原序列化(延伸com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer)无法以这种方式登录。
您需要使用覆写findReferenceDeserializer方法来注册com.fasterxml.jackson.databind.deser.Deserializers的自订实作。这个方法会呼叫,以寻找指涉型别之值的还原序列化程式。
您还需要添加com.fasterxml.jackson.databind.type.TypeModifier,它将把“泛型容器”类型修改为com.fasterxml.jackson.databind.type.ReferenceType,这样您的自定义ReferenceTypeDeserializer将被调用。
最简单的方法是注册一个自定义的com.fasterxml.jackson.databind.Module,它将addDeserializersaddTypeModifier。一个很好的例子是Jdk8Module,它为java.util.Optional和它的朋友做所有的工作。

为自定义泛型容器MyRef注册反序列化程序的完整示例:

MyRefDeserializer。这只是一个例子。被覆盖方法的具体实现(以及需要覆盖哪些方法)将取决于您的要求。

public final class MyRefDeserializer extends ReferenceTypeDeserializer<MyRef<?>> {

    public MyRefDeserializer(JavaType fullType, ValueInstantiator vi,
            TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
        super(fullType, vi, typeDeser, deser);
    }

    @Override
    protected MyRefDeserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
        return new MyRefDeserializer(_fullType, _valueInstantiator, typeDeser, valueDeser);
    }

    @Override
    public MyRef<?> getNullValue(DeserializationContext ctxt) throws JsonMappingException {
        return MyRef.of(_valueDeserializer.getNullValue(ctxt));
    }

    @Override
    public Object getAbsentValue(DeserializationContext ctxt) throws JsonMappingException {
        return MyRef.absent();
    }

    @Override
    public MyRef<?> referenceValue(Object contents) {
        return MyRef.of(contents);
    }

    @Override
    public Object getReferenced(MyRef<?> reference) {
        return reference.orElse(null);
    }

    @Override
    public NullableOptional<?> updateReference(MyRef<?> reference, Object contents) {
        return referenceValue(contents);
    }

}

Deserializers接口的实现:

public class MyRefDeserializers extends Deserializers.Base {

    @Override
    public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType, DeserializationConfig config,
            BeanDescription beanDesc, TypeDeserializer contentTypeDeserializer, JsonDeserializer<?> contentDeserializer)
            throws JsonMappingException {

        if (refType.hasRawClass(MyRef.class)) {
            return new MyRefDeserializer(refType, null, contentTypeDeserializer, contentDeserializer);
        }

        return null;
    }

}

TypeModifier的一种实现,它允许Jackson理解MyRef是一种ReferenceType类型:

public class MyRefTypeModifier extends TypeModifier {

    @Override
    public JavaType modifyType(JavaType type, Type jdkType, TypeBindings context, TypeFactory typeFactory) {
        if (type.isReferenceType() || type.isContainerType()) {
            return type;
        }

        if (type.getRawClass() == MyRef.class) {
            return ReferenceType.upgradeFrom(type, type.containedTypeOrUnknown(0));
        } else {
            return type;
        }

    }

}

最后是Module,它将注册反序列化器和类型修饰符:

public class MyRefModule extends Module {

    @Override
    public String getModuleName() {
        return "MyRefModule";
    }

    @Override
    public Version version() {
        return Version.unknownVersion();
    }

    @Override
    public void setupModule(SetupContext context) {
        context.addDeserializers(new MyRefDeserializers());
        context.addTypeModifier(new MyRefTypeModifier());
    }

}

然后,您需要使用ObjectMapper注册此模块。例如:

ObjectMapper = new ObjectMapper();
objectMapper.registerModule(new MyRefModule());

如果您使用Sping Boot ,则只需将模块公开为@Bean,它将自动注册为默认的ObjectMapper

vddsk6oq

vddsk6oq2#

我最终为我的反序列化器实现了一个不同的接口。

class PatchableDeserializer(private val valueType: Class<*>?): JsonDeserializer<Patchable<*>>(), ContextualDeserializer {
    override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
        val wrapperType = property?.type

        val rawClass = wrapperType?.containedType(0)?.rawClass
        return PatchableDeserializer(rawClass)
    }

    override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Patchable<*> =
        Patchable.of(p!!.readValueAs(valueType))

    override fun getNullValue(ctxt: DeserializationContext?): Patchable<Any> =
            if (ctxt?.parser?.currentToken == JsonToken.VALUE_NULL)
                Patchable.ofNull()
            else
                Patchable.undefined()
}

这可以正常工作,但是Jackson需要解析器中的上下文信息来使其工作,也就是说,上面的测试代码不起作用。但是,如果显式指定要反序列化到的DTO,并且该DTO具有正确的类型注解,它就可以工作。

相关问题