使用Jackson将JsonNode转特定类实例时出现StackOverflowError
问题场景
编写JSON入站数据包反序列化器时,通过post_type和meta_event_type字段区分不同数据包类型,对应实现了密封类层级结构:EventData -> MetaEventData -> LifecycleMetaEventData/HeartbeatEventData,并为父类添加自定义反序列化器动态选择子类。
但反序列化时触发StackOverflowError,原因是:在MetaEventDataDeserializer中调用node.deserializeTo<LifecycleMetaEventData>(mapper)时,由于LifecycleMetaEventData继承自MetaEventData,而MetaEventData标注了@JsonDeserialize(using = MetaEventDataDeserializer::class),Jackson会再次触发MetaEventDataDeserializer,形成无限循环调用。
优雅解决方案
方案1:利用Jackson原生多态反序列化(推荐)
直接使用Jackson的多态反序列化特性替代自定义反序列化器,代码更简洁且原生支持类型判断:
- 为
EventData添加多态配置:
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "post_type" // 用于区分类型的字段 ) @JsonSubTypes( JsonSubTypes.Type(value = MetaEventData::class, name = META_EVENT), // 可添加其他post_type对应的子类,比如MessageEventData::class ) sealed class EventData { abstract val postType: String }
- 为
MetaEventData添加多态配置:
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "meta_event_type" // 用于区分MetaEvent子类的字段 ) @JsonSubTypes( JsonSubTypes.Type(value = LifecycleMetaEventData::class, name = LIFECYCLE), JsonSubTypes.Type(value = HeartbeatEventData::class, name = HEARTBEAT) ) sealed class MetaEventData : EventData() { abstract val metaEventType: String }
- 移除所有自定义反序列化器类,以及类上的
@JsonDeserialize(using = ...)注解。
Jackson会自动根据配置的字段值选择对应子类完成反序列化,完全避免循环调用问题,代码维护成本更低。
方案2:临时覆盖父类反序列化器配置
如果无法使用多态反序列化,可以在反序列化子类时,临时创建移除了父类自定义反序列化器的ObjectMapper实例:
修改MetaEventDataDeserializer:
object MetaEventDataDeserializer : StdDeserializer<MetaEventData>(MetaEventData::class.java) { private fun readResolve(): Any = MetaEventDataDeserializer override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MetaEventData { val node = p.readValueAsTree<ObjectNode>() val mapper = p.codec as ObjectMapper // 创建临时ObjectMapper,覆盖MetaEventData的反序列化器配置 val tempMapper = mapper.copy().apply { configOverride(MetaEventData::class.java) .setDeserializerOverride(BeanDeserializerModifier.None) } return when (val metaEventType = node.getNotNull(META_EVENT_TYPE).asText()) { LIFECYCLE -> node.deserializeTo<LifecycleMetaEventData>(tempMapper) HEARTBEAT -> node.deserializeTo<HeartbeatEventData>(tempMapper) else -> throw IllegalArgumentException("Unexpected meta event type: $metaEventType") } } }
通过临时禁用MetaEventData的自定义反序列化器,Jackson会使用默认Bean反序列化器处理子类,避免循环调用。
方案3:直接解析为子类类型
使用treeToValue方法直接将节点转换为指定子类,绕过父类的自定义反序列化器:
修改MetaEventDataDeserializer:
object MetaEventDataDeserializer : StdDeserializer<MetaEventData>(MetaEventData::class.java) { private fun readResolve(): Any = MetaEventDataDeserializer override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MetaEventData { val node = p.readValueAsTree<ObjectNode>() val mapper = p.codec as ObjectMapper return when (val metaEventType = node.getNotNull(META_EVENT_TYPE).asText()) { LIFECYCLE -> mapper.treeToValue(node, LifecycleMetaEventData::class.java) HEARTBEAT -> mapper.treeToValue(node, HeartbeatEventData::class.java) else -> throw IllegalArgumentException("Unexpected meta event type: $metaEventType") } } }
treeToValue会直接针对目标子类进行反序列化,不会触发父类的自定义反序列化逻辑,简单直接解决循环问题。
内容的提问来源于stack exchange,提问作者Chuanwise

