当前位置: 首页 > news >正文

序列化和反序列化(redis为例)

Jackson @JsonTypeInfo 详解笔记

一、@JsonTypeInfo 作用

  • 场景:JSON 反序列化时 Jackson 不知道把一段 {} 转成哪个具体类。

  • 做法:在 JSON 里额外写一个类型标识,Jackson 根据这个标识找到对应类完成还原。

  • 注解位置字段上均可;只有出现多态的那一层才需要。


二、@JsonTypeInfo 全属性速查

属性取值说明举例
useId.CLASS用全限定类名作为类型 id"@clazz": "com.xiaoyu.Child"
Id.MINIMAL_CLASS去掉公共包前缀"@clazz": ".Child"(要求同包)
Id.NAME自定义名字(需配合 @JsonSubTypes"@type": "child"
Id.CUSTOM完全自定义解析器实现 TypeIdResolver
Id.NONE不额外写类型很少用
includeAs.PROPERTY(默认)新增一个同级属性{"@clazz":"xxx","name":"Tom"}
As.WRAPPER_OBJECT用类名做外层 key{"Child":{"name":"Tom"}}
As.WRAPPER_ARRAY整个对象变成 [id,{}]["com.xxx.Child",{"name":"Tom"}]
As.EXTERNAL_PROPERTY属性写在父级节点仅集合/Map 场景
As.EXISTING_PROPERTY复用已有字段字段值=类型 id
property任意字符串当 include=PROPERTY 时属性名默认 "@type",可改 "@clazz"
visibletrue/false反序列化后是否保留该属性值到 Java 字段默认 false
defaultImplClass<?>找不到类型 id 时回退的类defaultImpl = UnknownModel.class
@JsonTypeInfo
├─ use        → 用什么当类型 id(CLASS/NAME/MINIMAL_CLASS...)
├─ include    → id 放哪(PROPERTY / WRAPPER_OBJECT / EXTERNAL_PROPERTY...)
├─ property   → 属性名(默认 "@type")
├─ visible    → 反序列化后是否保留 id 值
└─ defaultImpl→ 找不到 id 时的兜底类

三、代码示例(覆盖全部常用组合)

1. CLASS + PROPERTY(最常用,零配置)

@Data
public class RedisDataDTO<T> {@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "@clazz")private T data;
}

JSON 结果

{"@clazz": "java.util.ArrayList","data": [1, 2, 3]
}

Redis序列化和反序列化 @ 配置 详解笔记

一、全局 activateDefaultTyping 会不会污染?

@Beanpublic ObjectMapper objectMapper() {ObjectMapper om =  new ObjectMapper().setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY).registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)// 关键:忽略 POJO 中不认识的字段.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);;/*** 不加这种全局改变的,会导致字段污染,直接修改DTO类,让他在写入redis前,加上类的信息即可。*   打开后,所有没有被 @JsonTypeInfo 显式覆盖的 非 final 类型(List、Map、你的自定义 DTO …)*   都会在 JSON 里多出一个 @class 字段。*/
//                .activateDefaultTyping(
//                        LaissezFaireSubTypeValidator.instance,
//                        ObjectMapper.DefaultTyping.NON_FINAL,
//                        JsonTypeInfo.As.PROPERTY);// 兼容 LocalDate 空格格式om.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("yyyy-MM-dd"));// 兼容 LocalDateTime 空格格式om.configOverride(LocalDateTime.class).setFormat(JsonFormat.Value.forPattern("yyyy-MM-dd HH:mm:ss"));return om;}

结论

  • 全局 activateDefaultTyping 会污染
    所有非 final 类型(List、Map、自定义 DTO)都会被加上 "@class" 字段。

  • 污染后果

    1. JSON 体积膨胀;

    2. 类路径一旦重构,老数据直接反序列化失败;

    3. 与前端/MQ 共用的 DTO 也会带 @class,属于无差别污染。

企业落地关闭全局开关只在必要字段手动加 @JsonTypeInfo,见第四部分代码。


二、@JsonTypeInfo 到底加在哪一层?

@JsonTypeInfo需不需要加

有多态就要加,运行期能100%确定类型,就不需要加,详细见本文最后的“TypeReference 与 @JsonTypeInfo 是否必须搭配”  章节

@Data
public class RedisDataDTO<T> {@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,      // 用全限定类名include = JsonTypeInfo.As.PROPERTY, // 以属性形式出现property = "@clazz")              // 属性名private List<T> data;private LocalDateTime expireTime;
}

规则

  • 只加在“会出现多态”的字段上

  • 不需要把 T 里面所有类都贴一遍;

  • 本例中 RedisDataDTO.data 字段是 T,运行时可能是 List/Map/Child 等未知类型,只在这一层加即可;

  • 反序列化时 Jackson 发现:

    1. 字段上有 @JsonTypeInfo

    2. JSON 里有 "@clazz": "com.xxx.Child"

    3. 用该字符串反射加载类并还原对象;

  • T 里面的普通字段/类无需任何注解,除非它们自身也可能多态(如 List<? extends BaseInterface>),那就在对应子字段再加一次。


三、TypeReference 干嘛的?

3.1、技术背景

  • Jackson convertValue 签名:
    <T> T convertValue(Object fromValue, JavaType/JavaTypeReference<T> toValueType)

  • Java 泛型运行时擦除,直接写
    mapper.convertValue(redisValue, RedisDataDTO.class)
    只能得到 RedisDataDTO<Object>,里面 List<Map<String, Child>> 会退化成 LinkedHashMap强转即 ClassCastException

  • TypeReference 利用匿名内部类保存完整泛型签名,让 Jackson 运行期仍知道:
    “我要的是 RedisDataDTO<List<Map<String, Child>>>
    → 一层层把 LinkedHashMap 转成真正的 Child 对象。

3.2、其它方法  JavaType ,  二者对比如下:

// 1. 同一数据源
Object redisValue = redisTemplate.opsForValue().get("order:1");// 2. JavaType 写法
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(RedisDataDTO.class, OrderDTO.class);
RedisDataDTO<OrderDTO> dto1 = objectMapper.convertValue(redisValue, javaType);// 3. TypeReference 写法
RedisDataDTO<OrderDTO> dto2 = objectMapper.convertValue(redisValue,new TypeReference<RedisDataDTO<OrderDTO>>() {});// 4. 验证
System.out.println(dto1.getData().getClass());   // class OrderDTO
System.out.println(dto2.getData().getClass());   // class OrderDTO
System.out.println(dto1.equals(dto2));           // true

3.3、二者 嵌套 3 层写法对比

需求List<Map<String, Set<OrderDetail>>>

  1. TypeReference(写死)

TypeReference<RedisDataDTO<List<Map<String, Set<OrderDetail>>>>> ref =new TypeReference<>() {};
var data = objectMapper.convertValue(redisValue, ref);
  1. JavaType(可动态拼)

JavaType inner = objectMapper.constructType(OrderDetail.class);
JavaType setType = objectMapper.getTypeFactory().constructParametricType(Set.class, inner);
JavaType mapType = objectMapper.getTypeFactory().constructParametricType(Map.class, objectMapper.constructType(String.class), setType);
JavaType listType = objectMapper.getTypeFactory().constructParametricType(List.class, mapType);
JavaType dtoType = objectMapper.getTypeFactory().constructParametricType(RedisDataDTO.class, listType);var data = objectMapper.convertValue(redisValue, dtoType);

3.4、一句话总结

  • 功能等价——都能完整还原泛型,不会退化成 LinkedHashMap

  • 选型看场景——
    编译期就确定TypeReference 简洁;
    运行期动态拼JavaType 灵活;

  • 性能差异可忽略可读性/可维护性才是选型重点。

3.5 、结论

没有 TypeReference 或者JavaType,嵌套泛型必然 ClassCastException。


四、零污染改造代码(部分代码参考)

1. 只污染 data 字段

@Data
public class RedisDataDTO<T> implements Serializable {private static final long serialVersionUID = 1L;/* 只在这一层打开多态,范围最小 */@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = As.PROPERTY, property = "@clazz")private T data;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime expireTime;
}

2. ObjectMapper 去掉全局开关

@Bean
public ObjectMapper objectMapper() {return new ObjectMapper().registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// ****** 不再调用 activateDefaultTyping ******
}

3. RedisTemplate、RedisUtil 保持原样

  • JSON 里只有 data 字段多一个 @clazz

  • 其它任何 DTO/VO 零污染


五、快速验证

// 写
List<Map<String, Set<OrderDetail>>> data = buildData();
redisUtil.set("k1", data, 60);// 读
var back = redisUtil.get("k1",new TypeReference<RedisDataDTO<List<Map<String, Set<OrderDetail>>>>>() {});System.out.println(back.getData().get(0).get("key").get(0).getClass());
// class OrderDetail  (不是 LinkedHashMap)

六、速记一句话

  1. 全局 activateDefaultTyping 别用字段级 @JsonTypeInfo 足够;

  2. @JsonTypeInfo 只贴在最外层“会多态”的字段”,子类/子字段无需重复贴;

  3. TypeReference 必须给,否则运行期泛型擦除 → LinkedHashMap 强转爆炸

  4. 改造后 只有必要字段多一个 @clazz零污染、可升级、可兼容

TypeReference 与 @JsonTypeInfo 是否必须搭配?

一、两者职责划分

功能技术是否必须
把泛型层数对齐
防止 List<Map<String,Child>> 退化成 LinkedHashMap
TypeReference 或 JavaType
(嵌套时必须)
把多态对象还原成正确子类
(Dog vs Cat / 实现类 vs 接口)
@JsonTypeInfo + @clazz/@type
(出现子类/实现类时必须)

“TypeReference 管层数,@JsonTypeInfo 管多态”
二者无强制绑定关系,按场景按需选用。


二、不需要 @JsonTypeInfo 的场景(无多态)

代码示例

@Data
public class RedisDataDTO<T> {private T data;          // 无 @JsonTypeInfo
}// 存
List<OrderDTO> list = List.of(new OrderDTO(1));
redisUtil.set("k1", list, 60);// 取
var back = objectMapper.convertValue(redisValue,new TypeReference<RedisDataDTO<List<OrderDTO>>>() {});

实际 JSON

{"data": [{ "id": 1, "name": "订单" }],"expireTime": "2025-06-20 15:30:00"
}
  • 运行期 100% 确定为 OrderDTO,无子类;  /**  关键  */

  • 结论TypeReference 已足够,不需要 @JsonTypeInfo


三、必须加 @JsonTypeInfo 的场景(有多态)

代码示例

@Data
public class RedisDataDTO<T> {@JsonTypeInfo(use = Id.CLASS, property = "@clazz")   // 必须加private T data;
}// 存:List 里放 Dog 和 Cat 两种子类
List<Animal> animals = List.of(new Dog(), new Cat());
redisUtil.set("k2", animals, 60);// 取
var back = objectMapper.convertValue(redisValue,new TypeReference<RedisDataDTO<List<Animal>>>() {});

实际 JSON

{"data": [{ "@clazz": "com.xiaoyu.Dog", "bone": 1 },{ "@clazz": "com.xiaoyu.Cat", "fish": 2 }],"expireTime": "2025-06-20 15:30:00"
}
  • 运行期可能是 Dog/Cat 任意子类;

  • 没有 @clazz Jackson 无法还原子类

  • 结论TypeReference 只管泛型层数,子类还原必须 @JsonTypeInfo


四、一句话速记

“TypeReference 管层数,@JsonTypeInfo 管多态;只要存子类/实现类,就必须加 @JsonTypeInfo,与用不用 TypeReference 无关。”

五、扩充

“JSON + @JsonTypeInfo + TypeReference/JavaType”这套组合拳在 Redis、RabbitMQ、Elasticsearch 里全部通用;
差异只在外围配置(MQ 白名单、ES 不写 @clazz),核心规则不变。

http://www.dtcms.com/a/438837.html

相关文章:

  • 之江汇学校网站建设wordpress 不显示评论
  • 洛谷 - 背包题目详解(超详细版)
  • 自主进化的AI大模型架构设想(解决大模型时效性问题):知识网络的拓扑设计
  • 网站建设所需费用明细应不应该购买老域名建设新网站
  • 突破传统!基于SAM架构的双模态图像分割:让AI“看见“红外与可见光的完美融合
  • Agentic Schemas:构建未来多智能体协作架构的实践蓝图
  • 血玥珏-MIDI音符合理性筛选处理器 v1.0.0.6 使用说明
  • 网站建设维诺之星抖音seo排名源码
  • 深入理解:Rust 的内存模型
  • 深圳建站公司推荐宣传片制作费用报价表
  • Zig 语言通用代码生成器:逻辑冒烟测试版五,数据库自动反射功能
  • 基于 GEE 制作研究区遥感影像可用性地图
  • 微PE | 辅助安装Window系统
  • 企业网站怎么维护易语言做试用点击网站
  • (单调栈)洛谷 P6875 COCI 2013/2014 KRUŽNICE 题解
  • 地图网站怎么做中国的外贸企业有哪些
  • 外贸公司网站怎么设计更好单页响应式网站模板
  • 恒生电子面经准备
  • 电视剧在线观看完整版免费网站网友让你建网站做商城
  • 大学网站群建设方案设计网名姓氏
  • Qt 按钮点击事件全链路解析:从系统驱动到槽函数
  • 外贸公司建网站一般多少钱京津冀协同发展现状
  • 开发网站 语言优秀营销软文范例300字
  • 木匠手做网站网站主体变更
  • 领码方案 | 掌控研发管理成熟度:从理论透视到AI驱动的实战进阶
  • 为什么学网站开发互联网最吃香的职业
  • MTK调试-耳机驱动
  • Go语言中的map
  • 国土系统网站建设用地受理表花垣县建设局网站
  • 网站建设报告内容合肥经开区建设局网站