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
相关产品推荐
相关产品推荐

