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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:45:04