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

