Jackson反序列化Long键Map失败,Redis序列化场景下如何解决?
问题解决方案
一、解决Long类型键的Map反序列化问题
原因分析
JSON规范中键只能是字符串类型,Jackson默认会将Map<Long, T>的键序列化为字符串,反序列化时也会保留String类型,导致强转后用Long键无法获取值。同时你启用了默认类型激活(DefaultTyping.EVERYTHING),也会影响泛型类型的处理逻辑。
解决方案
1. 自定义Long类型的KeyDeserializer
实现KeyDeserializer,将字符串键转换为Long类型:
import com.fasterxml.jackson.databind.KeyDeserializer import com.fasterxml.jackson.databind.DeserializationContext class LongKeyDeserializer : KeyDeserializer() { override fun deserializeKey(key: String, ctxt: DeserializationContext): Long? { return try { key.toLong() } catch (e: NumberFormatException) { ctxt.handleWeirdKey(Long::class.java, key, "无法将键解析为Long类型") } } }
2. 配置ObjectMapper并注册KeyDeserializer
修改objectMapper()方法,添加自定义模块:
import com.fasterxml.jackson.databind.module.SimpleModule private fun objectMapper(): ObjectMapper { val longKeyModule = SimpleModule().apply { addKeyDeserializer(Long::class.java, LongKeyDeserializer()) } return ObjectMapper() .registerModule(JavaTimeModule()) .registerModule(KotlinModule()) .registerModule(longKeyModule) .activateDefaultTyping( BasicPolymorphicTypeValidator.builder().allowIfBaseType(Any::class.java).build(), ObjectMapper.DefaultTyping.EVERYTHING ) }
3. 反序列化时用TypeReference指定泛型类型
避免直接强转Map::class.java,改用TypeReference明确目标类型:
@Test fun hello() { val objectMapper = objectMapper() val json = objectMapper.writeValueAsString(mapOf(1L to "hi", 2L to "hello")) val map: Map<Long, String> = objectMapper.readValue( json, object : TypeReference<Map<Long, String>>() {} ) assertThat(map[1L]).isEqualTo("hi") }
二、解决RedisSerializer泛型反序列化问题
原因分析
Kotlin泛型存在类型擦除,运行时无法直接获取T的实际类型,因此直接使用object : TypeReference<T>() {}无法正确识别目标类型。
解决方案
在RedisSerializer实现类的构造函数中传入具体的TypeReference,提前固化目标类型信息:
1. 实现带类型参数的GenericRedisSerializer
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.type.TypeReference import org.springframework.data.redis.serializer.RedisSerializer import java.io.ByteArrayOutputStream import java.io.IOException import java.util.zip.GZIPInputStream class GenericRedisSerializer<T>( private val objectMapper: ObjectMapper, private val typeReference: TypeReference<T> ) : RedisSerializer<T> { override fun serialize(t: T?): ByteArray? { if (t == null) return null return try { objectMapper.writeValueAsBytes(t) } catch (e: IOException) { throw IllegalStateException("序列化对象失败", e) } } override fun deserialize(bytes: ByteArray?): T? { if (bytes == null) return null return try { if (isGzipCompressed(bytes)) { val byteArrayHolder = decodeGzip(bytes) objectMapper.readValue( byteArrayHolder.rawByteArray, 0, byteArrayHolder.limit, typeReference ) } else { objectMapper.readValue(bytes, typeReference) } } catch (e: IOException) { throw IllegalStateException("反序列化对象失败", e) } } private fun isGzipCompressed(bytes: ByteArray): Boolean { return bytes.size >= 2 && bytes[0] == 0x1f.toByte() && bytes[1] == 0x8b.toByte() } private fun decodeGzip(bytes: ByteArray): ByteArrayHolder { GZIPInputStream(bytes.inputStream()).use { gis -> val buffer = ByteArrayOutputStream() gis.copyTo(buffer) return ByteArrayHolder(buffer.toByteArray(), buffer.size()) } } data class ByteArrayHolder(val rawByteArray: ByteArray, val limit: Int) }
2. 使用时传入具体的TypeReference
创建Serializer实例时,明确指定目标泛型的TypeReference:
// 针对Map<Long, String>的序列化器 val mapSerializer = GenericRedisSerializer<Map<Long, String>>( objectMapper(), object : TypeReference<Map<Long, String>>() {} ) // 注册到RedisTemplate val redisTemplate = RedisTemplate<String, Map<Long, String>>().apply { setValueSerializer(mapSerializer) setKeySerializer(RedisSerializer.string()) }
扩展:固定类型的Serializer子类
如果某类类型需要频繁使用,可以单独实现子类简化调用:
class MapLongStringRedisSerializer(objectMapper: ObjectMapper) : GenericRedisSerializer<Map<Long, String>>( objectMapper, object : TypeReference<Map<Long, String>>() {} )
内容的提问来源于stack exchange,提问作者MangKyu
相关产品推荐
相关产品推荐

