ObjectMapper处理Map<Range,List>序列化反序列化抛出ClassCastException
问题根因
你的问题既不是操作疏漏也不是框架Bug,本质是两个底层规则限制导致的:
- JSON语法强制要求所有键必须为字符串类型:Jackson序列化非String类型的Map键时,会默认调用该对象的
toString()方法转为字符串存储,你输出的序列化结果里的键就是Range对象的toString返回值,本身就不是结构化的Range对象。 - 泛型擦除导致Jackson无法获取Map的真实泛型信息:你反序列化时直接传入
Map.class作为目标类型,运行时泛型信息已经被擦除,Jackson不知道你需要把key反序列化为Range<Instant>类型,默认就会反序列化为String,你代码里声明的Map<Range<Instant>, List<String>>只是编译期的类型提示,对运行时的Jackson没有任何约束,因此取key时会触发类型转换异常。
你之前注册的GuavaModule只支持Range作为值的反序列化,没有提供Range作为Map键的反序列化逻辑,所以不会生效。
解决方案
需要两步操作修复:
- 自定义针对
Range<Instant>的键反序列化器,适配你序列化生成的Range字符串格式
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.KeyDeserializer; import com.google.common.collect.Range; import java.io.IOException; import java.time.Instant; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RangeInstantKeyDeserializer extends KeyDeserializer { // 匹配你输出的闭区间Range字符串格式,如[2021-11-25T08:00:00Z..2021-11-26T08:00:00Z] private static final Pattern CLOSED_RANGE_PATTERN = Pattern.compile("\\[(.*?)\\.\\.(.*?)\\]"); @Override public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { Matcher matcher = CLOSED_RANGE_PATTERN.matcher(key); if (!matcher.matches()) { throw new IOException("无法识别的Range键格式: " + key); } Instant lowerBound = Instant.parse(matcher.group(1).trim()); Instant upperBound = Instant.parse(matcher.group(2).trim()); return Range.closed(lowerBound, upperBound); } }
- 注册自定义反序列化器,反序列化时传入完整的泛型类型信息
// 原有模块注册逻辑保留 mapper.registerModule(new Jdk8Module()); mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new GuavaModule()); // 注册自定义Range键反序列化器 SimpleModule customModule = new SimpleModule(); customModule.addKeyDeserializer(Range.class, new RangeInstantKeyDeserializer()); mapper.registerModule(customModule); // 用TypeReference传递完整泛型信息,代替原来的Map.class TypeReference<Map<Range<Instant>, List<String>>> typeRef = new TypeReference<Map<Range<Instant>, List<String>>>() {}; Map<Range<Instant>, List<String>> deserializedMap = mapper.readValue(serializedMap, typeRef);
修改完成后再执行取key的逻辑就不会抛出类型转换异常。
注意事项
如果你的业务场景中存在开区间、左开右闭、右开左闭等其他Range类型,只需要调整正则匹配规则,根据边界的括号/方括号标识调用对应Range.open/Range.closedOpen等构造方法即可。
内容的提问来源于stack exchange,提问作者S K
相关产品推荐
相关产品推荐

