You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson ContextualDeserializer解析嵌套泛型Kotlin类时无法获取contextualType

问题背景

我实现了一个实现ContextualDeserializer接口的自定义JsonDeserializer,用于反序列化泛型Kotlin类MyObject<out Content>。反序列化MyObject<String>时一切正常,但处理嵌套泛型的MyObject<MyObject<String>>时失败:

  • contextualType.containedType(0)返回null
  • 用contentType.rawClass反序列化会丢失泛型信息,导致内容被解析为MyObject而非MyObject<String>

实际场景是开发支持多序列化框架(Gson、Jackson、Kotlinx)的库,必须用自定义反序列化器,不能把序列化逻辑嵌入实体类。


问题代码示例

实体类

data class MyObject<out Content>(val content: Content)

原自定义反序列化器

class MyObjectDeserializer : JsonDeserializer<MyObject<*>>, ContextualDeserializer {
    private var contentDeserializer: JsonDeserializer<*>? = null

    constructor() {}
    constructor(contentDeserializer: JsonDeserializer<*>?) {
        this.contentDeserializer = contentDeserializer
    }

    override fun deserialize(
        json: JsonParser,
        context: DeserializationContext
    ): MyObject<*> {
        val node = json.codec.readTree(json)
        val contentNode = node.get("content")
        val content = contentDeserializer?.deserialize(contentNode.traverse(), context)
        return MyObject(content)
    }

    override fun createContextual(
        prov: DeserializationContext,
        property: BeanProperty?
    ): JsonDeserializer<*> {
        val contextualType = property?.type ?: prov.contextualType
        // 嵌套时这里containedType(0)返回null
        val contentType = contextualType.containedType(0) ?: return this
        val contentDeserializer = prov.findRootValueDeserializer(contentType)
        return MyObjectDeserializer(contentDeserializer)
    }
}

注册方式

val objectMapper = ObjectMapper().registerModule(KotlinModule())
objectMapper.registerModule(SimpleModule().addDeserializer(MyObject::class.java, MyObjectDeserializer()))

测试用例

@Test
fun testNestedMyObject() {
    val json = """{"content":{"content":"hello"}}"""
    // 期望得到MyObject(MyObject("hello")),实际得到MyObject(MyObject(null)或MyObject(LinkedHashMap))
    val result = objectMapper.readValue(json, object : TypeReference<MyObject<MyObject<String>>>() {})
    assert(result.content is MyObject<*>)
    assert((result.content as MyObject<*>).content == "hello") // 这里断言失败
}

原因分析

处理嵌套泛型MyObject<MyObject<String>>时,contextualType虽为ParameterizedType,但直接调用containedType(0)可能因Jackson类型处理逻辑限制,无法正确获取嵌套泛型参数;若使用contentType.rawClass,会完全丢失内层泛型信息,导致反序列化内层MyObject时无法识别其String类型的content。


解决方案

核心是递归处理泛型类型,用JavaType保留完整泛型信息,确保嵌套类型的参数不丢失。修改后的反序列化器如下:

class MyObjectDeserializer : JsonDeserializer<MyObject<*>>, ContextualDeserializer {
    private var contentJavaType: JavaType? = null
    private var contentDeserializer: JsonDeserializer<*>? = null

    constructor() {}
    constructor(contentJavaType: JavaType, contentDeserializer: JsonDeserializer<*>?) {
        this.contentJavaType = contentJavaType
        this.contentDeserializer = contentDeserializer
    }

    override fun deserialize(
        json: JsonParser,
        context: DeserializationContext
    ): MyObject<*> {
        val node = json.codec.readTree(json)
        val contentNode = node.get("content") ?: return MyObject(null)
        val content = contentDeserializer?.deserialize(contentNode.traverse(), context)
        return MyObject(content)
    }

    override fun createContextual(
        prov: DeserializationContext,
        property: BeanProperty?
    ): JsonDeserializer<*> {
        val currentType = property?.type ?: prov.contextualType
        // 递归处理参数化类型,支持嵌套泛型
        if (currentType is ParameterizedType) {
            val contentType = currentType.containedType(0)
            if (contentType != null) {
                // 为内层泛型类型也创建对应的反序列化器
                val contentDeserializer = prov.findRootValueDeserializer(contentType)
                return MyObjectDeserializer(contentType, contentDeserializer)
            }
        }
        // 无法获取泛型信息时的降级处理
        return this
    }
}

测试验证

修改后的反序列化器可正确解析嵌套泛型,测试用例中的断言会通过:result.content会被解析为MyObject<String>,其content值为"hello"。


关键注意点

  • 必须使用JavaType而非rawClass,完整保留泛型层级信息
  • 在createContextual中递归处理参数化类型,确保内层泛型类也能被正确解析
  • 确保Jackson的KotlinModule已正确注册,支持Kotlin泛型与空安全特性

内容的提问来源于stack exchange,提问作者Jan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:55:22