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

Kotlin中使用Jackson JsonSubType实现嵌套子类反序列化失败问题

问题描述

我有一个消息投递系统,支持推送通知、SMS等类型,每种类型还包含各自的子类型(例如SMS分为Irancell、Mediana等)。我希望使用JsonSubType识别消息对应的类并调用对应发送机制,但序列化MedianaSmsMessage后,无法将其反序列化为MessageForDelivery类型。

相关代码

MedianaSmsMessage类

class MedianaSmsMessage(
    override val receiver: String,
    override val message: Message,
    override val app: App,
    override val requester: DeliveryRequester,
    override val deliveryProviderId: String,
    override val smsDeliveryType: String = "MEDIANA",
) : SmsMessageForDelivery()

SmsMessageForDelivery抽象类

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "smsDeliveryType", visible = true)
@JsonSubTypes(
    JsonSubTypes.Type(value = MedianaSmsMessage::class, name = "MEDIANA"),
    JsonSubTypes.Type(value = IrancellSmsMessage::class, name = "IRANCELL"),
    JsonSubTypes.Type(value = MobinnetSmsMessage::class, name = "MOBINNET"),
)
abstract class SmsMessageForDelivery : MessageForDelivery() {
    abstract override val receiver: String
    abstract override val message: Message
    override val deliveryType: DeliveryType = DeliveryType.SMS
    abstract override val app: App
    abstract override val requester: DeliveryRequester
    abstract override val deliveryProviderId: String
    abstract val smsDeliveryType: String
}

MessageForDelivery抽象类

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "deliveryType", visible = true)
@JsonSubTypes(
    JsonSubTypes.Type(value = PushMessageForDelivery::class, name = "APP_PUSH"),
    JsonSubTypes.Type(value = WebHookMessageForDelivery::class, name = "WEBHOOK"),
    JsonSubTypes.Type(value = EmailMessageForDelivery::class, name = "EMAIL"),
    JsonSubTypes.Type(value = SmsMessageForDelivery::class, name = "SMS"),
)

abstract class MessageForDelivery {
    abstract val receiver: String
    abstract val message: Message
    abstract val deliveryType: DeliveryType
    abstract val app: App
    abstract val requester: DeliveryRequester
    abstract val deliveryProviderId: String
}

测试代码

@PostConstruct
fun test(){
    val content = "{\"smsDeliveryType\":\"MEDIANA\",\"receiver\":\"+9891312331\",\"message\":{\"title\":\"sms-title\",\"body\":\"test\",\"link\":null,\"url\":null,\"icon\":null,\"images\":[],\"buttons\":[]},\"app\":{\"appId\":\"test\"},\"requester\":{\"type\":\"CAMPAIGN\",\"id\":\"63e8b35acedd7f451fd0da14\"},\"deliveryProviderId\":\"Mediana\",\"deliveryType\":\"SMS\"}"
    val medianaSms = mapper.readValue(
        content,
        MedianaSmsMessage::class.java
    )
    val t = mapper.writeValueAsString(medianaSms)
    val test = mapper.readValue(t, MessageForDelivery::class.java)
    println(test)
}

错误信息

无法构造`ir.pegahtech.metrix.delivery.push.services.models.message.sms.SmsMessageForDelivery`的实例(不存在默认构造函数等创建器):抽象类型要么需要映射到具体类型,要么有自定义反序列化器,或者包含额外的类型信息

解决方案

问题根源是Jackson仅完成了MessageForDelivery到SmsMessageForDelivery的第一层多态解析,但SmsMessageForDelivery作为抽象类,Jackson无法自动识别其下的具体子类(如MedianaSmsMessage),以下是三种可行解决办法:

方案一:直接在顶层父类注册所有SMS子类

修改MessageForDelivery的@JsonSubTypes注解,把所有SMS具体子类直接加入注册列表,让Jackson能通过deliveryType和smsDeliveryType的组合完成完整解析:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "deliveryType", visible = true)
@JsonSubTypes(
    JsonSubTypes.Type(value = PushMessageForDelivery::class, name = "APP_PUSH"),
    JsonSubTypes.Type(value = WebHookMessageForDelivery::class, name = "WEBHOOK"),
    JsonSubTypes.Type(value = EmailMessageForDelivery::class, name = "EMAIL"),
    // 直接注册所有SMS具体子类
    JsonSubTypes.Type(value = MedianaSmsMessage::class, name = "SMS"),
    JsonSubTypes.Type(value = IrancellSmsMessage::class, name = "SMS"),
    JsonSubTypes.Type(value = MobinnetSmsMessage::class, name = "SMS"),
)
abstract class MessageForDelivery {
    // 原有代码不变
}

方案二:启用全局多态类型解析

配置ObjectMapper启用全局默认类型解析,Jackson会自动在序列化JSON中添加类型标识字段,无需手动维护子类列表,但会改变JSON结构:

val mapper = ObjectMapper()
    .registerModule(KotlinModule())
    .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)

方案三:自定义反序列化器

编写自定义反序列化器,手动根据deliveryType和smsDeliveryType字段选择对应的具体类,灵活性最高:

class MessageForDeliveryDeserializer : StdDeserializer<MessageForDelivery>(MessageForDelivery::class.java) {
    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MessageForDelivery {
        val node = p.codec.readTree<JsonNode>(p)
        val deliveryType = node["deliveryType"].asText()
        return when (deliveryType) {
            "SMS" -> {
                val smsType = node["smsDeliveryType"].asText()
                when (smsType) {
                    "MEDIANA" -> p.codec.treeToValue(node, MedianaSmsMessage::class.java)
                    "IRANCELL" -> p.codec.treeToValue(node, IrancellSmsMessage::class.java)
                    "MOBINNET" -> p.codec.treeToValue(node, MobinnetSmsMessage::class.java)
                    else -> throw IllegalArgumentException("未知SMS类型: $smsType")
                }
            }
            "APP_PUSH" -> p.codec.treeToValue(node, PushMessageForDelivery::class.java)
            "WEBHOOK" -> p.codec.treeToValue(node, WebHookMessageForDelivery::class.java)
            "EMAIL" -> p.codec.treeToValue(node, EmailMessageForDelivery::class.java)
            else -> throw IllegalArgumentException("未知投递类型: $deliveryType")
        }
    }
}

然后在MessageForDelivery类上添加注解指定反序列化器:

@JsonDeserialize(using = MessageForDeliveryDeserializer::class)
abstract class MessageForDelivery {
    // 原有代码不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 00:45:06